File indexing completed on 2024-05-19 06:01:40
0001 /* 0002 * Fingerprintjs2 2.1.0 - Modern & flexible browser fingerprint library v2 0003 * https://github.com/Valve/fingerprintjs2 0004 * Copyright (c) 2015 Valentin Vasilyev (valentin.vasilyev@outlook.com) 0005 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 0006 * 0007 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 0008 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 0009 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 0010 * ARE DISCLAIMED. IN NO EVENT SHALL VALENTIN VASILYEV BE LIABLE FOR ANY 0011 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 0012 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 0013 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 0014 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0015 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 0016 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0017 */ 0018 /* global define */ 0019 (function (name, context, definition) { 0020 'use strict' 0021 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() } 0022 })('Fingerprint2', this, function () { 0023 'use strict' 0024 0025 /// MurmurHash3 related functions 0026 0027 // 0028 // Given two 64bit ints (as an array of two 32bit ints) returns the two 0029 // added together as a 64bit int (as an array of two 32bit ints). 0030 // 0031 var x64Add = function (m, n) { 0032 m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff] 0033 n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff] 0034 var o = [0, 0, 0, 0] 0035 o[3] += m[3] + n[3] 0036 o[2] += o[3] >>> 16 0037 o[3] &= 0xffff 0038 o[2] += m[2] + n[2] 0039 o[1] += o[2] >>> 16 0040 o[2] &= 0xffff 0041 o[1] += m[1] + n[1] 0042 o[0] += o[1] >>> 16 0043 o[1] &= 0xffff 0044 o[0] += m[0] + n[0] 0045 o[0] &= 0xffff 0046 return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]] 0047 } 0048 0049 // 0050 // Given two 64bit ints (as an array of two 32bit ints) returns the two 0051 // multiplied together as a 64bit int (as an array of two 32bit ints). 0052 // 0053 var x64Multiply = function (m, n) { 0054 m = [m[0] >>> 16, m[0] & 0xffff, m[1] >>> 16, m[1] & 0xffff] 0055 n = [n[0] >>> 16, n[0] & 0xffff, n[1] >>> 16, n[1] & 0xffff] 0056 var o = [0, 0, 0, 0] 0057 o[3] += m[3] * n[3] 0058 o[2] += o[3] >>> 16 0059 o[3] &= 0xffff 0060 o[2] += m[2] * n[3] 0061 o[1] += o[2] >>> 16 0062 o[2] &= 0xffff 0063 o[2] += m[3] * n[2] 0064 o[1] += o[2] >>> 16 0065 o[2] &= 0xffff 0066 o[1] += m[1] * n[3] 0067 o[0] += o[1] >>> 16 0068 o[1] &= 0xffff 0069 o[1] += m[2] * n[2] 0070 o[0] += o[1] >>> 16 0071 o[1] &= 0xffff 0072 o[1] += m[3] * n[1] 0073 o[0] += o[1] >>> 16 0074 o[1] &= 0xffff 0075 o[0] += (m[0] * n[3]) + (m[1] * n[2]) + (m[2] * n[1]) + (m[3] * n[0]) 0076 o[0] &= 0xffff 0077 return [(o[0] << 16) | o[1], (o[2] << 16) | o[3]] 0078 } 0079 // 0080 // Given a 64bit int (as an array of two 32bit ints) and an int 0081 // representing a number of bit positions, returns the 64bit int (as an 0082 // array of two 32bit ints) rotated left by that number of positions. 0083 // 0084 var x64Rotl = function (m, n) { 0085 n %= 64 0086 if (n === 32) { 0087 return [m[1], m[0]] 0088 } else if (n < 32) { 0089 return [(m[0] << n) | (m[1] >>> (32 - n)), (m[1] << n) | (m[0] >>> (32 - n))] 0090 } else { 0091 n -= 32 0092 return [(m[1] << n) | (m[0] >>> (32 - n)), (m[0] << n) | (m[1] >>> (32 - n))] 0093 } 0094 } 0095 // 0096 // Given a 64bit int (as an array of two 32bit ints) and an int 0097 // representing a number of bit positions, returns the 64bit int (as an 0098 // array of two 32bit ints) shifted left by that number of positions. 0099 // 0100 var x64LeftShift = function (m, n) { 0101 n %= 64 0102 if (n === 0) { 0103 return m 0104 } else if (n < 32) { 0105 return [(m[0] << n) | (m[1] >>> (32 - n)), m[1] << n] 0106 } else { 0107 return [m[1] << (n - 32), 0] 0108 } 0109 } 0110 // 0111 // Given two 64bit ints (as an array of two 32bit ints) returns the two 0112 // xored together as a 64bit int (as an array of two 32bit ints). 0113 // 0114 var x64Xor = function (m, n) { 0115 return [m[0] ^ n[0], m[1] ^ n[1]] 0116 } 0117 // 0118 // Given a block, returns murmurHash3's final x64 mix of that block. 0119 // (`[0, h[0] >>> 1]` is a 33 bit unsigned right shift. This is the 0120 // only place where we need to right shift 64bit ints.) 0121 // 0122 var x64Fmix = function (h) { 0123 h = x64Xor(h, [0, h[0] >>> 1]) 0124 h = x64Multiply(h, [0xff51afd7, 0xed558ccd]) 0125 h = x64Xor(h, [0, h[0] >>> 1]) 0126 h = x64Multiply(h, [0xc4ceb9fe, 0x1a85ec53]) 0127 h = x64Xor(h, [0, h[0] >>> 1]) 0128 return h 0129 } 0130 0131 // 0132 // Given a string and an optional seed as an int, returns a 128 bit 0133 // hash using the x64 flavor of MurmurHash3, as an unsigned hex. 0134 // 0135 var x64hash128 = function (key, seed) { 0136 key = key || '' 0137 seed = seed || 0 0138 var remainder = key.length % 16 0139 var bytes = key.length - remainder 0140 var h1 = [0, seed] 0141 var h2 = [0, seed] 0142 var k1 = [0, 0] 0143 var k2 = [0, 0] 0144 var c1 = [0x87c37b91, 0x114253d5] 0145 var c2 = [0x4cf5ad43, 0x2745937f] 0146 for (var i = 0; i < bytes; i = i + 16) { 0147 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)] 0148 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)] 0149 k1 = x64Multiply(k1, c1) 0150 k1 = x64Rotl(k1, 31) 0151 k1 = x64Multiply(k1, c2) 0152 h1 = x64Xor(h1, k1) 0153 h1 = x64Rotl(h1, 27) 0154 h1 = x64Add(h1, h2) 0155 h1 = x64Add(x64Multiply(h1, [0, 5]), [0, 0x52dce729]) 0156 k2 = x64Multiply(k2, c2) 0157 k2 = x64Rotl(k2, 33) 0158 k2 = x64Multiply(k2, c1) 0159 h2 = x64Xor(h2, k2) 0160 h2 = x64Rotl(h2, 31) 0161 h2 = x64Add(h2, h1) 0162 h2 = x64Add(x64Multiply(h2, [0, 5]), [0, 0x38495ab5]) 0163 } 0164 k1 = [0, 0] 0165 k2 = [0, 0] 0166 switch (remainder) { 0167 case 15: 0168 k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 14)], 48)) 0169 // fallthrough 0170 case 14: 0171 k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 13)], 40)) 0172 // fallthrough 0173 case 13: 0174 k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 12)], 32)) 0175 // fallthrough 0176 case 12: 0177 k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 11)], 24)) 0178 // fallthrough 0179 case 11: 0180 k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 10)], 16)) 0181 // fallthrough 0182 case 10: 0183 k2 = x64Xor(k2, x64LeftShift([0, key.charCodeAt(i + 9)], 8)) 0184 // fallthrough 0185 case 9: 0186 k2 = x64Xor(k2, [0, key.charCodeAt(i + 8)]) 0187 k2 = x64Multiply(k2, c2) 0188 k2 = x64Rotl(k2, 33) 0189 k2 = x64Multiply(k2, c1) 0190 h2 = x64Xor(h2, k2) 0191 // fallthrough 0192 case 8: 0193 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 7)], 56)) 0194 // fallthrough 0195 case 7: 0196 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 6)], 48)) 0197 // fallthrough 0198 case 6: 0199 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 5)], 40)) 0200 // fallthrough 0201 case 5: 0202 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 4)], 32)) 0203 // fallthrough 0204 case 4: 0205 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 3)], 24)) 0206 // fallthrough 0207 case 3: 0208 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 2)], 16)) 0209 // fallthrough 0210 case 2: 0211 k1 = x64Xor(k1, x64LeftShift([0, key.charCodeAt(i + 1)], 8)) 0212 // fallthrough 0213 case 1: 0214 k1 = x64Xor(k1, [0, key.charCodeAt(i)]) 0215 k1 = x64Multiply(k1, c1) 0216 k1 = x64Rotl(k1, 31) 0217 k1 = x64Multiply(k1, c2) 0218 h1 = x64Xor(h1, k1) 0219 // fallthrough 0220 } 0221 h1 = x64Xor(h1, [0, key.length]) 0222 h2 = x64Xor(h2, [0, key.length]) 0223 h1 = x64Add(h1, h2) 0224 h2 = x64Add(h2, h1) 0225 h1 = x64Fmix(h1) 0226 h2 = x64Fmix(h2) 0227 h1 = x64Add(h1, h2) 0228 h2 = x64Add(h2, h1) 0229 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) 0230 } 0231 0232 var defaultOptions = { 0233 preprocessor: null, 0234 audio: { 0235 timeout: 1000, 0236 // On iOS 11, audio context can only be used in response to user interaction. 0237 // We require users to explicitly enable audio fingerprinting on iOS 11. 0238 // See https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088 0239 excludeIOS11: true 0240 }, 0241 fonts: { 0242 swfContainerId: 'fingerprintjs2', 0243 swfPath: 'flash/compiled/FontList.swf', 0244 userDefinedFonts: [], 0245 extendedJsFonts: false 0246 }, 0247 screen: { 0248 // To ensure consistent fingerprints when users rotate their mobile devices 0249 detectScreenOrientation: true 0250 }, 0251 plugins: { 0252 sortPluginsFor: [/palemoon/i], 0253 excludeIE: false 0254 }, 0255 extraComponents: [], 0256 excludes: { 0257 // Unreliable on Windows, see https://github.com/Valve/fingerprintjs2/issues/375 0258 'enumerateDevices': true, 0259 // devicePixelRatio depends on browser zoom, and it's impossible to detect browser zoom 0260 'pixelRatio': true, 0261 // DNT depends on incognito mode for some browsers (Chrome) and it's impossible to detect incognito mode 0262 'doNotTrack': true, 0263 // uses js fonts already 0264 'fontsFlash': true 0265 }, 0266 NOT_AVAILABLE: 'not available', 0267 ERROR: 'error', 0268 EXCLUDED: 'excluded' 0269 } 0270 0271 var each = function (obj, iterator) { 0272 if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { 0273 obj.forEach(iterator) 0274 } else if (obj.length === +obj.length) { 0275 for (var i = 0, l = obj.length; i < l; i++) { 0276 iterator(obj[i], i, obj) 0277 } 0278 } else { 0279 for (var key in obj) { 0280 if (obj.hasOwnProperty(key)) { 0281 iterator(obj[key], key, obj) 0282 } 0283 } 0284 } 0285 } 0286 0287 var map = function (obj, iterator) { 0288 var results = [] 0289 // Not using strict equality so that this acts as a 0290 // shortcut to checking for `null` and `undefined`. 0291 if (obj == null) { 0292 return results 0293 } 0294 if (Array.prototype.map && obj.map === Array.prototype.map) { return obj.map(iterator) } 0295 each(obj, function (value, index, list) { 0296 results.push(iterator(value, index, list)) 0297 }) 0298 return results 0299 } 0300 0301 var extendSoft = function (target, source) { 0302 if (source == null) { return target } 0303 var value 0304 var key 0305 for (key in source) { 0306 value = source[key] 0307 if (value != null && !(Object.prototype.hasOwnProperty.call(target, key))) { 0308 target[key] = value 0309 } 0310 } 0311 return target 0312 } 0313 0314 // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices 0315 var enumerateDevicesKey = function (done, options) { 0316 if (!isEnumerateDevicesSupported()) { 0317 return done(options.NOT_AVAILABLE) 0318 } 0319 navigator.mediaDevices.enumerateDevices().then(function (devices) { 0320 done(devices.map(function (device) { 0321 return 'id=' + device.deviceId + ';gid=' + device.groupId + ';' + device.kind + ';' + device.label 0322 })) 0323 }) 0324 .catch(function (error) { 0325 done(error) 0326 }) 0327 } 0328 0329 var isEnumerateDevicesSupported = function () { 0330 return (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) 0331 } 0332 // Inspired by and based on https://github.com/cozylife/audio-fingerprint 0333 var audioKey = function (done, options) { 0334 var audioOptions = options.audio 0335 if (audioOptions.excludeIOS11 && navigator.userAgent.match(/OS 11.+Version\/11.+Safari/)) { 0336 // See comment for excludeUserAgent and https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088 0337 return done(options.EXCLUDED) 0338 } 0339 0340 var AudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext 0341 0342 if (AudioContext == null) { 0343 return done(options.NOT_AVAILABLE) 0344 } 0345 0346 var context = new AudioContext(1, 44100, 44100) 0347 0348 var oscillator = context.createOscillator() 0349 oscillator.type = 'triangle' 0350 oscillator.frequency.setValueAtTime(10000, context.currentTime) 0351 0352 var compressor = context.createDynamicsCompressor() 0353 each([ 0354 ['threshold', -50], 0355 ['knee', 40], 0356 ['ratio', 12], 0357 ['reduction', -20], 0358 ['attack', 0], 0359 ['release', 0.25] 0360 ], function (item) { 0361 if (compressor[item[0]] !== undefined && typeof compressor[item[0]].setValueAtTime === 'function') { 0362 compressor[item[0]].setValueAtTime(item[1], context.currentTime) 0363 } 0364 }) 0365 0366 oscillator.connect(compressor) 0367 compressor.connect(context.destination) 0368 oscillator.start(0) 0369 context.startRendering() 0370 0371 var audioTimeoutId = setTimeout(function () { 0372 console.warn('Audio fingerprint timed out. Please report bug at https://github.com/Valve/fingerprintjs2 with your user agent: "' + navigator.userAgent + '".') 0373 context.oncomplete = function () { } 0374 context = null 0375 return done('audioTimeout') 0376 }, audioOptions.timeout) 0377 0378 context.oncomplete = function (event) { 0379 var fingerprint 0380 try { 0381 clearTimeout(audioTimeoutId) 0382 fingerprint = event.renderedBuffer.getChannelData(0) 0383 .slice(4500, 5000) 0384 .reduce(function (acc, val) { return acc + Math.abs(val) }, 0) 0385 .toString() 0386 oscillator.disconnect() 0387 compressor.disconnect() 0388 } catch (error) { 0389 done(error) 0390 return 0391 } 0392 done(fingerprint) 0393 } 0394 } 0395 var UserAgent = function (done) { 0396 done(navigator.userAgent) 0397 } 0398 var webdriver = function (done, options) { 0399 done(navigator.webdriver == null ? options.NOT_AVAILABLE : navigator.webdriver) 0400 } 0401 var languageKey = function (done, options) { 0402 done(navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage || options.NOT_AVAILABLE) 0403 } 0404 var colorDepthKey = function (done, options) { 0405 done(window.screen.colorDepth || options.NOT_AVAILABLE) 0406 } 0407 var deviceMemoryKey = function (done, options) { 0408 done(navigator.deviceMemory || options.NOT_AVAILABLE) 0409 } 0410 var pixelRatioKey = function (done, options) { 0411 done(window.devicePixelRatio || options.NOT_AVAILABLE) 0412 } 0413 var screenResolutionKey = function (done, options) { 0414 done(getScreenResolution(options)) 0415 } 0416 var getScreenResolution = function (options) { 0417 var resolution = [window.screen.width, window.screen.height] 0418 if (options.screen.detectScreenOrientation) { 0419 resolution.sort().reverse() 0420 } 0421 return resolution 0422 } 0423 var availableScreenResolutionKey = function (done, options) { 0424 done(getAvailableScreenResolution(options)) 0425 } 0426 var getAvailableScreenResolution = function (options) { 0427 if (window.screen.availWidth && window.screen.availHeight) { 0428 var available = [window.screen.availHeight, window.screen.availWidth] 0429 if (options.screen.detectScreenOrientation) { 0430 available.sort().reverse() 0431 } 0432 return available 0433 } 0434 // headless browsers 0435 return options.NOT_AVAILABLE 0436 } 0437 var timezoneOffset = function (done) { 0438 done(new Date().getTimezoneOffset()) 0439 } 0440 var timezone = function (done, options) { 0441 if (window.Intl && window.Intl.DateTimeFormat) { 0442 done(new window.Intl.DateTimeFormat().resolvedOptions().timeZone) 0443 return 0444 } 0445 done(options.NOT_AVAILABLE) 0446 } 0447 var sessionStorageKey = function (done, options) { 0448 done(hasSessionStorage(options)) 0449 } 0450 var localStorageKey = function (done, options) { 0451 done(hasLocalStorage(options)) 0452 } 0453 var indexedDbKey = function (done, options) { 0454 done(hasIndexedDB(options)) 0455 } 0456 var addBehaviorKey = function (done) { 0457 // body might not be defined at this point or removed programmatically 0458 done(!!(document.body && document.body.addBehavior)) 0459 } 0460 var openDatabaseKey = function (done) { 0461 done(!!window.openDatabase) 0462 } 0463 var cpuClassKey = function (done, options) { 0464 done(getNavigatorCpuClass(options)) 0465 } 0466 var platformKey = function (done, options) { 0467 done(getNavigatorPlatform(options)) 0468 } 0469 var doNotTrackKey = function (done, options) { 0470 done(getDoNotTrack(options)) 0471 } 0472 var canvasKey = function (done, options) { 0473 if (isCanvasSupported()) { 0474 done(getCanvasFp(options)) 0475 return 0476 } 0477 done(options.NOT_AVAILABLE) 0478 } 0479 var webglKey = function (done, options) { 0480 if (isWebGlSupported()) { 0481 done(getWebglFp()) 0482 return 0483 } 0484 done(options.NOT_AVAILABLE) 0485 } 0486 var webglVendorAndRendererKey = function (done) { 0487 if (isWebGlSupported()) { 0488 done(getWebglVendorAndRenderer()) 0489 return 0490 } 0491 done() 0492 } 0493 var adBlockKey = function (done) { 0494 done(getAdBlock()) 0495 } 0496 var hasLiedLanguagesKey = function (done) { 0497 done(getHasLiedLanguages()) 0498 } 0499 var hasLiedResolutionKey = function (done) { 0500 done(getHasLiedResolution()) 0501 } 0502 var hasLiedOsKey = function (done) { 0503 done(getHasLiedOs()) 0504 } 0505 var hasLiedBrowserKey = function (done) { 0506 done(getHasLiedBrowser()) 0507 } 0508 // flash fonts (will increase fingerprinting time 20X to ~ 130-150ms) 0509 var flashFontsKey = function (done, options) { 0510 // we do flash if swfobject is loaded 0511 if (!hasSwfObjectLoaded()) { 0512 return done('swf object not loaded') 0513 } 0514 if (!hasMinFlashInstalled()) { 0515 return done('flash not installed') 0516 } 0517 if (!options.fonts.swfPath) { 0518 return done('missing options.fonts.swfPath') 0519 } 0520 loadSwfAndDetectFonts(function (fonts) { 0521 done(fonts) 0522 }, options) 0523 } 0524 // kudos to http://www.lalit.org/lab/javascript-css-font-detect/ 0525 var jsFontsKey = function (done, options) { 0526 // a font will be compared against all the three default fonts. 0527 // and if it doesn't match all 3 then that font is not available. 0528 var baseFonts = ['monospace', 'sans-serif', 'serif'] 0529 0530 var fontList = [ 0531 'Andale Mono', 'Arial', 'Arial Black', 'Arial Hebrew', 'Arial MT', 'Arial Narrow', 'Arial Rounded MT Bold', 'Arial Unicode MS', 0532 'Bitstream Vera Sans Mono', 'Book Antiqua', 'Bookman Old Style', 0533 'Calibri', 'Cambria', 'Cambria Math', 'Century', 'Century Gothic', 'Century Schoolbook', 'Comic Sans', 'Comic Sans MS', 'Consolas', 'Courier', 'Courier New', 0534 'Geneva', 'Georgia', 0535 'Helvetica', 'Helvetica Neue', 0536 'Impact', 0537 'Lucida Bright', 'Lucida Calligraphy', 'Lucida Console', 'Lucida Fax', 'LUCIDA GRANDE', 'Lucida Handwriting', 'Lucida Sans', 'Lucida Sans Typewriter', 'Lucida Sans Unicode', 0538 'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS Outlook', 'MS PGothic', 'MS Reference Sans Serif', 'MS Sans Serif', 'MS Serif', 'MYRIAD', 'MYRIAD PRO', 0539 'Palatino', 'Palatino Linotype', 0540 'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Light', 'Segoe UI Semibold', 'Segoe UI Symbol', 0541 'Tahoma', 'Times', 'Times New Roman', 'Times New Roman PS', 'Trebuchet MS', 0542 'Verdana', 'Wingdings', 'Wingdings 2', 'Wingdings 3' 0543 ] 0544 0545 if (options.fonts.extendedJsFonts) { 0546 var extendedFontList = [ 0547 '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', 0548 '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', 0549 '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', 0550 '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', 0551 'Blackadder ITC', 'BlairMdITC TT', 'Bodoni 72', 'Bodoni 72 Oldstyle', 'Bodoni 72 Smallcaps', 'Bodoni MT', 'Bodoni MT Black', 'Bodoni MT Condensed', 'Bodoni MT Poster Compressed', 0552 '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', 0553 'CaslonOpnface BT', 'Castellar', 'Centaur', 'Cezanne', 'CG Omega', 'CG Times', 'Chalkboard', 'Chalkboard SE', 'Chalkduster', 'Charlesworth', 'Charter Bd BT', 'Charter BT', 'Chaucer', 0554 'ChelthmITC Bk BT', 'Chiller', 'Clarendon', 'Clarendon Condensed', 'CloisterBlack BT', 'Cochin', 'Colonna MT', 'Constantia', 'Cooper Black', 'Copperplate', 'Copperplate Gothic', 'Copperplate Gothic Bold', 0555 'Copperplate Gothic Light', 'CopperplGoth Bd BT', 'Corbel', 'Cordia New', 'CordiaUPC', 'Cornerstone', 'Coronet', 'Cuckoo', 'Curlz MT', 'DaunPenh', 'Dauphin', 'David', 'DB LCD Temp', 'DELICIOUS', 'Denmark', 0556 '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', 0557 'EucrosiaUPC', 'Euphemia', 'Euphemia UCAS', 'EUROSTILE', 'Exotc350 Bd BT', 'FangSong', 'Felix Titling', 'Fixedsys', 'FONTIN', 'Footlight MT Light', 'Forte', 0558 'FrankRuehl', 'Fransiscan', 'Freefrm721 Blk BT', 'FreesiaUPC', 'Freestyle Script', 'French Script MT', 'FrnkGothITC Bk BT', 'Fruitger', 'FRUTIGER', 0559 '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', 0560 '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', 0561 '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', 0562 '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', 0563 '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', 0564 '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', 0565 'Lydian BT', 'Magneto', 'Maiandra GD', 'Malayalam Sangam MN', 'Malgun Gothic', 0566 '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', 0567 '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', 0568 'MONO', 'MoolBoran', 'Mrs Eaves', 'MS LineDraw', 'MS Mincho', 'MS PMincho', 'MS Reference Specialty', 'MS UI Gothic', 'MT Extra', 'MUSEO', 'MV Boli', 0569 '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', 0570 'OSAKA', 'OzHandicraft BT', 'Palace Script MT', 'Papyrus', 'Parchment', 'Party LET', 'Pegasus', 'Perpetua', 'Perpetua Titling MT', 'PetitaBold', 'Pickwick', 'Plantagenet Cherokee', 'Playbill', 'PMingLiU', 'PMingLiU-ExtB', 0571 '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', 0572 'Santa Fe LET', 'Savoye LET', 'Sceptre', 'Script', 'Script MT Bold', 'SCRIPTINA', 'Serifa', 'Serifa BT', 'Serifa Th BT', 'ShelleyVolante BT', 'Sherwood', 0573 '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', 0574 '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', 0575 'Terminal', 'Thonburi', 'Traditional Arabic', 'Trajan', 'TRAJAN PRO', 'Tristan', 'Tubular', 'Tunga', 'Tw Cen MT', 'Tw Cen MT Condensed', 'Tw Cen MT Condensed Extra Bold', 0576 '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', 0577 'ZapfEllipt BT', 'ZapfHumnst BT', 'ZapfHumnst Dm BT', 'Zapfino', 'Zurich BlkEx BT', 'Zurich Ex BT', 'ZWAdobeF'] 0578 fontList = fontList.concat(extendedFontList) 0579 } 0580 0581 fontList = fontList.concat(options.fonts.userDefinedFonts) 0582 0583 // remove duplicate fonts 0584 fontList = fontList.filter(function (font, position) { 0585 return fontList.indexOf(font) === position 0586 }) 0587 0588 // we use m or w because these two characters take up the maximum width. 0589 // And we use a LLi so that the same matching fonts can get separated 0590 var testString = 'mmmmmmmmmmlli' 0591 0592 // we test using 72px font size, we may use any size. I guess larger the better. 0593 var testSize = '72px' 0594 0595 var h = document.getElementsByTagName('body')[0] 0596 0597 // div to load spans for the base fonts 0598 var baseFontsDiv = document.createElement('div') 0599 0600 // div to load spans for the fonts to detect 0601 var fontsDiv = document.createElement('div') 0602 0603 var defaultWidth = {} 0604 var defaultHeight = {} 0605 0606 // creates a span where the fonts will be loaded 0607 var createSpan = function () { 0608 var s = document.createElement('span') 0609 /* 0610 * We need this css as in some weird browser this 0611 * span elements shows up for a microSec which creates a 0612 * bad user experience 0613 */ 0614 s.style.position = 'absolute' 0615 s.style.left = '-9999px' 0616 s.style.fontSize = testSize 0617 0618 // css font reset to reset external styles 0619 s.style.fontStyle = 'normal' 0620 s.style.fontWeight = 'normal' 0621 s.style.letterSpacing = 'normal' 0622 s.style.lineBreak = 'auto' 0623 s.style.lineHeight = 'normal' 0624 s.style.textTransform = 'none' 0625 s.style.textAlign = 'left' 0626 s.style.textDecoration = 'none' 0627 s.style.textShadow = 'none' 0628 s.style.whiteSpace = 'normal' 0629 s.style.wordBreak = 'normal' 0630 s.style.wordSpacing = 'normal' 0631 0632 s.innerHTML = testString 0633 return s 0634 } 0635 0636 // creates a span and load the font to detect and a base font for fallback 0637 var createSpanWithFonts = function (fontToDetect, baseFont) { 0638 var s = createSpan() 0639 s.style.fontFamily = "'" + fontToDetect + "'," + baseFont 0640 return s 0641 } 0642 0643 // creates spans for the base fonts and adds them to baseFontsDiv 0644 var initializeBaseFontsSpans = function () { 0645 var spans = [] 0646 for (var index = 0, length = baseFonts.length; index < length; index++) { 0647 var s = createSpan() 0648 s.style.fontFamily = baseFonts[index] 0649 baseFontsDiv.appendChild(s) 0650 spans.push(s) 0651 } 0652 return spans 0653 } 0654 0655 // creates spans for the fonts to detect and adds them to fontsDiv 0656 var initializeFontsSpans = function () { 0657 var spans = {} 0658 for (var i = 0, l = fontList.length; i < l; i++) { 0659 var fontSpans = [] 0660 for (var j = 0, numDefaultFonts = baseFonts.length; j < numDefaultFonts; j++) { 0661 var s = createSpanWithFonts(fontList[i], baseFonts[j]) 0662 fontsDiv.appendChild(s) 0663 fontSpans.push(s) 0664 } 0665 spans[fontList[i]] = fontSpans // Stores {fontName : [spans for that font]} 0666 } 0667 return spans 0668 } 0669 0670 // checks if a font is available 0671 var isFontAvailable = function (fontSpans) { 0672 var detected = false 0673 for (var i = 0; i < baseFonts.length; i++) { 0674 detected = (fontSpans[i].offsetWidth !== defaultWidth[baseFonts[i]] || fontSpans[i].offsetHeight !== defaultHeight[baseFonts[i]]) 0675 if (detected) { 0676 return detected 0677 } 0678 } 0679 return detected 0680 } 0681 0682 // create spans for base fonts 0683 var baseFontsSpans = initializeBaseFontsSpans() 0684 0685 // add the spans to the DOM 0686 h.appendChild(baseFontsDiv) 0687 0688 // get the default width for the three base fonts 0689 for (var index = 0, length = baseFonts.length; index < length; index++) { 0690 defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth // width for the default font 0691 defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight // height for the default font 0692 } 0693 0694 // create spans for fonts to detect 0695 var fontsSpans = initializeFontsSpans() 0696 0697 // add all the spans to the DOM 0698 h.appendChild(fontsDiv) 0699 0700 // check available fonts 0701 var available = [] 0702 for (var i = 0, l = fontList.length; i < l; i++) { 0703 if (isFontAvailable(fontsSpans[fontList[i]])) { 0704 available.push(fontList[i]) 0705 } 0706 } 0707 0708 // remove spans from DOM 0709 h.removeChild(fontsDiv) 0710 h.removeChild(baseFontsDiv) 0711 done(available) 0712 } 0713 var pluginsComponent = function (done, options) { 0714 if (isIE()) { 0715 if (!options.plugins.excludeIE) { 0716 done(getIEPlugins(options)) 0717 } else { 0718 done(options.EXCLUDED) 0719 } 0720 } else { 0721 done(getRegularPlugins(options)) 0722 } 0723 } 0724 var getRegularPlugins = function (options) { 0725 if (navigator.plugins == null) { 0726 return options.NOT_AVAILABLE 0727 } 0728 0729 var plugins = [] 0730 // plugins isn't defined in Node envs. 0731 for (var i = 0, l = navigator.plugins.length; i < l; i++) { 0732 if (navigator.plugins[i]) { plugins.push(navigator.plugins[i]) } 0733 } 0734 0735 // sorting plugins only for those user agents, that we know randomize the plugins 0736 // every time we try to enumerate them 0737 if (pluginsShouldBeSorted(options)) { 0738 plugins = plugins.sort(function (a, b) { 0739 if (a.name > b.name) { return 1 } 0740 if (a.name < b.name) { return -1 } 0741 return 0 0742 }) 0743 } 0744 return map(plugins, function (p) { 0745 var mimeTypes = map(p, function (mt) { 0746 return [mt.type, mt.suffixes] 0747 }) 0748 return [p.name, p.description, mimeTypes] 0749 }) 0750 } 0751 var getIEPlugins = function (options) { 0752 var result = [] 0753 if ((Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, 'ActiveXObject')) || ('ActiveXObject' in window)) { 0754 var names = [ 0755 'AcroPDF.PDF', // Adobe PDF reader 7+ 0756 'Adodb.Stream', 0757 'AgControl.AgControl', // Silverlight 0758 'DevalVRXCtrl.DevalVRXCtrl.1', 0759 'MacromediaFlashPaper.MacromediaFlashPaper', 0760 'Msxml2.DOMDocument', 0761 'Msxml2.XMLHTTP', 0762 'PDF.PdfCtrl', // Adobe PDF reader 6 and earlier, brrr 0763 'QuickTime.QuickTime', // QuickTime 0764 'QuickTimeCheckObject.QuickTimeCheck.1', 0765 'RealPlayer', 0766 'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)', 0767 'RealVideo.RealVideo(tm) ActiveX Control (32-bit)', 0768 'Scripting.Dictionary', 0769 'SWCtl.SWCtl', // ShockWave player 0770 'Shell.UIHelper', 0771 'ShockwaveFlash.ShockwaveFlash', // flash plugin 0772 'Skype.Detection', 0773 'TDCCtl.TDCCtl', 0774 'WMPlayer.OCX', // Windows media player 0775 'rmocx.RealPlayer G2 Control', 0776 'rmocx.RealPlayer G2 Control.1' 0777 ] 0778 // starting to detect plugins in IE 0779 result = map(names, function (name) { 0780 try { 0781 // eslint-disable-next-line no-new 0782 new window.ActiveXObject(name) 0783 return name 0784 } catch (e) { 0785 return options.ERROR 0786 } 0787 }) 0788 } else { 0789 result.push(options.NOT_AVAILABLE) 0790 } 0791 if (navigator.plugins) { 0792 result = result.concat(getRegularPlugins(options)) 0793 } 0794 return result 0795 } 0796 var pluginsShouldBeSorted = function (options) { 0797 var should = false 0798 for (var i = 0, l = options.plugins.sortPluginsFor.length; i < l; i++) { 0799 var re = options.plugins.sortPluginsFor[i] 0800 if (navigator.userAgent.match(re)) { 0801 should = true 0802 break 0803 } 0804 } 0805 return should 0806 } 0807 var touchSupportKey = function (done) { 0808 done(getTouchSupport()) 0809 } 0810 var hardwareConcurrencyKey = function (done, options) { 0811 done(getHardwareConcurrency(options)) 0812 } 0813 var hasSessionStorage = function (options) { 0814 try { 0815 return !!window.sessionStorage 0816 } catch (e) { 0817 return options.ERROR // SecurityError when referencing it means it exists 0818 } 0819 } 0820 0821 // https://bugzilla.mozilla.org/show_bug.cgi?id=781447 0822 var hasLocalStorage = function (options) { 0823 try { 0824 return !!window.localStorage 0825 } catch (e) { 0826 return options.ERROR // SecurityError when referencing it means it exists 0827 } 0828 } 0829 var hasIndexedDB = function (options) { 0830 try { 0831 return !!window.indexedDB 0832 } catch (e) { 0833 return options.ERROR // SecurityError when referencing it means it exists 0834 } 0835 } 0836 var getHardwareConcurrency = function (options) { 0837 if (navigator.hardwareConcurrency) { 0838 return navigator.hardwareConcurrency 0839 } 0840 return options.NOT_AVAILABLE 0841 } 0842 var getNavigatorCpuClass = function (options) { 0843 return navigator.cpuClass || options.NOT_AVAILABLE 0844 } 0845 var getNavigatorPlatform = function (options) { 0846 if (navigator.platform) { 0847 return navigator.platform 0848 } else { 0849 return options.NOT_AVAILABLE 0850 } 0851 } 0852 var getDoNotTrack = function (options) { 0853 if (navigator.doNotTrack) { 0854 return navigator.doNotTrack 0855 } else if (navigator.msDoNotTrack) { 0856 return navigator.msDoNotTrack 0857 } else if (window.doNotTrack) { 0858 return window.doNotTrack 0859 } else { 0860 return options.NOT_AVAILABLE 0861 } 0862 } 0863 // This is a crude and primitive touch screen detection. 0864 // It's not possible to currently reliably detect the availability of a touch screen 0865 // with a JS, without actually subscribing to a touch event. 0866 // http://www.stucox.com/blog/you-cant-detect-a-touchscreen/ 0867 // https://github.com/Modernizr/Modernizr/issues/548 0868 // method returns an array of 3 values: 0869 // maxTouchPoints, the success or failure of creating a TouchEvent, 0870 // and the availability of the 'ontouchstart' property 0871 0872 var getTouchSupport = function () { 0873 var maxTouchPoints = 0 0874 var touchEvent 0875 if (typeof navigator.maxTouchPoints !== 'undefined') { 0876 maxTouchPoints = navigator.maxTouchPoints 0877 } else if (typeof navigator.msMaxTouchPoints !== 'undefined') { 0878 maxTouchPoints = navigator.msMaxTouchPoints 0879 } 0880 try { 0881 document.createEvent('TouchEvent') 0882 touchEvent = true 0883 } catch (_) { 0884 touchEvent = false 0885 } 0886 var touchStart = 'ontouchstart' in window 0887 return [maxTouchPoints, touchEvent, touchStart] 0888 } 0889 // https://www.browserleaks.com/canvas#how-does-it-work 0890 0891 var getCanvasFp = function (options) { 0892 var result = [] 0893 // Very simple now, need to make it more complex (geo shapes etc) 0894 var canvas = document.createElement('canvas') 0895 canvas.width = 2000 0896 canvas.height = 200 0897 canvas.style.display = 'inline' 0898 var ctx = canvas.getContext('2d') 0899 // detect browser support of canvas winding 0900 // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/ 0901 // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/canvas/winding.js 0902 ctx.rect(0, 0, 10, 10) 0903 ctx.rect(2, 2, 6, 6) 0904 result.push('canvas winding:' + ((ctx.isPointInPath(5, 5, 'evenodd') === false) ? 'yes' : 'no')) 0905 0906 ctx.textBaseline = 'alphabetic' 0907 ctx.fillStyle = '#f60' 0908 ctx.fillRect(125, 1, 62, 20) 0909 ctx.fillStyle = '#069' 0910 // https://github.com/Valve/fingerprintjs2/issues/66 0911 if (options.dontUseFakeFontInCanvas) { 0912 ctx.font = '11pt Arial' 0913 } else { 0914 ctx.font = '11pt no-real-font-123' 0915 } 0916 ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 2, 15) 0917 ctx.fillStyle = 'rgba(102, 204, 0, 0.2)' 0918 ctx.font = '18pt Arial' 0919 ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 4, 45) 0920 0921 // canvas blending 0922 // http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ 0923 // http://jsfiddle.net/NDYV8/16/ 0924 ctx.globalCompositeOperation = 'multiply' 0925 ctx.fillStyle = 'rgb(255,0,255)' 0926 ctx.beginPath() 0927 ctx.arc(50, 50, 50, 0, Math.PI * 2, true) 0928 ctx.closePath() 0929 ctx.fill() 0930 ctx.fillStyle = 'rgb(0,255,255)' 0931 ctx.beginPath() 0932 ctx.arc(100, 50, 50, 0, Math.PI * 2, true) 0933 ctx.closePath() 0934 ctx.fill() 0935 ctx.fillStyle = 'rgb(255,255,0)' 0936 ctx.beginPath() 0937 ctx.arc(75, 100, 50, 0, Math.PI * 2, true) 0938 ctx.closePath() 0939 ctx.fill() 0940 ctx.fillStyle = 'rgb(255,0,255)' 0941 // canvas winding 0942 // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/ 0943 // http://jsfiddle.net/NDYV8/19/ 0944 ctx.arc(75, 75, 75, 0, Math.PI * 2, true) 0945 ctx.arc(75, 75, 25, 0, Math.PI * 2, true) 0946 ctx.fill('evenodd') 0947 0948 if (canvas.toDataURL) { result.push('canvas fp:' + canvas.toDataURL()) } 0949 return result 0950 } 0951 var getWebglFp = function () { 0952 var gl 0953 var fa2s = function (fa) { 0954 gl.clearColor(0.0, 0.0, 0.0, 1.0) 0955 gl.enable(gl.DEPTH_TEST) 0956 gl.depthFunc(gl.LEQUAL) 0957 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 0958 return '[' + fa[0] + ', ' + fa[1] + ']' 0959 } 0960 var maxAnisotropy = function (gl) { 0961 var ext = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') 0962 if (ext) { 0963 var anisotropy = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) 0964 if (anisotropy === 0) { 0965 anisotropy = 2 0966 } 0967 return anisotropy 0968 } else { 0969 return null 0970 } 0971 } 0972 0973 gl = getWebglCanvas() 0974 if (!gl) { return null } 0975 // WebGL fingerprinting is a combination of techniques, found in MaxMind antifraud script & Augur fingerprinting. 0976 // First it draws a gradient object with shaders and convers the image to the Base64 string. 0977 // 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 0978 // 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. 0979 var result = [] 0980 var vShaderTemplate = 'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}' 0981 var fShaderTemplate = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}' 0982 var vertexPosBuffer = gl.createBuffer() 0983 gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer) 0984 var vertices = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.732134444, 0]) 0985 gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) 0986 vertexPosBuffer.itemSize = 3 0987 vertexPosBuffer.numItems = 3 0988 var program = gl.createProgram() 0989 var vshader = gl.createShader(gl.VERTEX_SHADER) 0990 gl.shaderSource(vshader, vShaderTemplate) 0991 gl.compileShader(vshader) 0992 var fshader = gl.createShader(gl.FRAGMENT_SHADER) 0993 gl.shaderSource(fshader, fShaderTemplate) 0994 gl.compileShader(fshader) 0995 gl.attachShader(program, vshader) 0996 gl.attachShader(program, fshader) 0997 gl.linkProgram(program) 0998 gl.useProgram(program) 0999 program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex') 1000 program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset') 1001 gl.enableVertexAttribArray(program.vertexPosArray) 1002 gl.vertexAttribPointer(program.vertexPosAttrib, vertexPosBuffer.itemSize, gl.FLOAT, !1, 0, 0) 1003 gl.uniform2f(program.offsetUniform, 1, 1) 1004 gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems) 1005 try { 1006 result.push(gl.canvas.toDataURL()) 1007 } catch (e) { 1008 /* .toDataURL may be absent or broken (blocked by extension) */ 1009 } 1010 result.push('extensions:' + (gl.getSupportedExtensions() || []).join(';')) 1011 result.push('webgl aliased line width range:' + fa2s(gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE))) 1012 result.push('webgl aliased point size range:' + fa2s(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE))) 1013 result.push('webgl alpha bits:' + gl.getParameter(gl.ALPHA_BITS)) 1014 result.push('webgl antialiasing:' + (gl.getContextAttributes().antialias ? 'yes' : 'no')) 1015 result.push('webgl blue bits:' + gl.getParameter(gl.BLUE_BITS)) 1016 result.push('webgl depth bits:' + gl.getParameter(gl.DEPTH_BITS)) 1017 result.push('webgl green bits:' + gl.getParameter(gl.GREEN_BITS)) 1018 result.push('webgl max anisotropy:' + maxAnisotropy(gl)) 1019 result.push('webgl max combined texture image units:' + gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS)) 1020 result.push('webgl max cube map texture size:' + gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)) 1021 result.push('webgl max fragment uniform vectors:' + gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS)) 1022 result.push('webgl max render buffer size:' + gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)) 1023 result.push('webgl max texture image units:' + gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)) 1024 result.push('webgl max texture size:' + gl.getParameter(gl.MAX_TEXTURE_SIZE)) 1025 result.push('webgl max varying vectors:' + gl.getParameter(gl.MAX_VARYING_VECTORS)) 1026 result.push('webgl max vertex attribs:' + gl.getParameter(gl.MAX_VERTEX_ATTRIBS)) 1027 result.push('webgl max vertex texture image units:' + gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS)) 1028 result.push('webgl max vertex uniform vectors:' + gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS)) 1029 result.push('webgl max viewport dims:' + fa2s(gl.getParameter(gl.MAX_VIEWPORT_DIMS))) 1030 result.push('webgl red bits:' + gl.getParameter(gl.RED_BITS)) 1031 result.push('webgl renderer:' + gl.getParameter(gl.RENDERER)) 1032 result.push('webgl shading language version:' + gl.getParameter(gl.SHADING_LANGUAGE_VERSION)) 1033 result.push('webgl stencil bits:' + gl.getParameter(gl.STENCIL_BITS)) 1034 result.push('webgl vendor:' + gl.getParameter(gl.VENDOR)) 1035 result.push('webgl version:' + gl.getParameter(gl.VERSION)) 1036 1037 try { 1038 // Add the unmasked vendor and unmasked renderer if the debug_renderer_info extension is available 1039 var extensionDebugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info') 1040 if (extensionDebugRendererInfo) { 1041 result.push('webgl unmasked vendor:' + gl.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL)) 1042 result.push('webgl unmasked renderer:' + gl.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL)) 1043 } 1044 } catch (e) { /* squelch */ } 1045 1046 if (!gl.getShaderPrecisionFormat) { 1047 return result 1048 } 1049 1050 each(['FLOAT', 'INT'], function (numType) { 1051 each(['VERTEX', 'FRAGMENT'], function (shader) { 1052 each(['HIGH', 'MEDIUM', 'LOW'], function (numSize) { 1053 each(['precision', 'rangeMin', 'rangeMax'], function (key) { 1054 var format = gl.getShaderPrecisionFormat(gl[shader + '_SHADER'], gl[numSize + '_' + numType])[key] 1055 if (key !== 'precision') { 1056 key = 'precision ' + key 1057 } 1058 var line = ['webgl ', shader.toLowerCase(), ' shader ', numSize.toLowerCase(), ' ', numType.toLowerCase(), ' ', key, ':', format].join('') 1059 result.push(line) 1060 }) 1061 }) 1062 }) 1063 }) 1064 return result 1065 } 1066 var getWebglVendorAndRenderer = function () { 1067 /* This a subset of the WebGL fingerprint with a lot of entropy, while being reasonably browser-independent */ 1068 try { 1069 var glContext = getWebglCanvas() 1070 var extensionDebugRendererInfo = glContext.getExtension('WEBGL_debug_renderer_info') 1071 return glContext.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL) + '~' + glContext.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL) 1072 } catch (e) { 1073 return null 1074 } 1075 } 1076 var getAdBlock = function () { 1077 var ads = document.createElement('div') 1078 ads.innerHTML = ' ' 1079 ads.className = 'adsbox' 1080 var result = false 1081 try { 1082 // body may not exist, that's why we need try/catch 1083 document.body.appendChild(ads) 1084 result = document.getElementsByClassName('adsbox')[0].offsetHeight === 0 1085 document.body.removeChild(ads) 1086 } catch (e) { 1087 result = false 1088 } 1089 return result 1090 } 1091 var getHasLiedLanguages = function () { 1092 // We check if navigator.language is equal to the first language of navigator.languages 1093 // navigator.languages is undefined on IE11 (and potentially older IEs) 1094 if (typeof navigator.languages !== 'undefined') { 1095 try { 1096 var firstLanguages = navigator.languages[0].substr(0, 2) 1097 if (firstLanguages !== navigator.language.substr(0, 2)) { 1098 return true 1099 } 1100 } catch (err) { 1101 return true 1102 } 1103 } 1104 return false 1105 } 1106 var getHasLiedResolution = function () { 1107 return window.screen.width < window.screen.availWidth || window.screen.height < window.screen.availHeight 1108 } 1109 var getHasLiedOs = function () { 1110 var userAgent = navigator.userAgent.toLowerCase() 1111 var oscpu = navigator.oscpu 1112 var platform = navigator.platform.toLowerCase() 1113 var os 1114 // We extract the OS from the user agent (respect the order of the if else if statement) 1115 if (userAgent.indexOf('windows phone') >= 0) { 1116 os = 'Windows Phone' 1117 } else if (userAgent.indexOf('win') >= 0) { 1118 os = 'Windows' 1119 } else if (userAgent.indexOf('android') >= 0) { 1120 os = 'Android' 1121 } else if (userAgent.indexOf('linux') >= 0 || userAgent.indexOf('cros') >= 0) { 1122 os = 'Linux' 1123 } else if (userAgent.indexOf('iphone') >= 0 || userAgent.indexOf('ipad') >= 0) { 1124 os = 'iOS' 1125 } else if (userAgent.indexOf('mac') >= 0) { 1126 os = 'Mac' 1127 } else { 1128 os = 'Other' 1129 } 1130 // We detect if the person uses a mobile device 1131 var mobileDevice = (('ontouchstart' in window) || 1132 (navigator.maxTouchPoints > 0) || 1133 (navigator.msMaxTouchPoints > 0)) 1134 1135 if (mobileDevice && os !== 'Windows Phone' && os !== 'Android' && os !== 'iOS' && os !== 'Other') { 1136 return true 1137 } 1138 1139 // We compare oscpu with the OS extracted from the UA 1140 if (typeof oscpu !== 'undefined') { 1141 oscpu = oscpu.toLowerCase() 1142 if (oscpu.indexOf('win') >= 0 && os !== 'Windows' && os !== 'Windows Phone') { 1143 return true 1144 } else if (oscpu.indexOf('linux') >= 0 && os !== 'Linux' && os !== 'Android') { 1145 return true 1146 } else if (oscpu.indexOf('mac') >= 0 && os !== 'Mac' && os !== 'iOS') { 1147 return true 1148 } else if ((oscpu.indexOf('win') === -1 && oscpu.indexOf('linux') === -1 && oscpu.indexOf('mac') === -1) !== (os === 'Other')) { 1149 return true 1150 } 1151 } 1152 1153 // We compare platform with the OS extracted from the UA 1154 if (platform.indexOf('win') >= 0 && os !== 'Windows' && os !== 'Windows Phone') { 1155 return true 1156 } else if ((platform.indexOf('linux') >= 0 || platform.indexOf('android') >= 0 || platform.indexOf('pike') >= 0) && os !== 'Linux' && os !== 'Android') { 1157 return true 1158 } else if ((platform.indexOf('mac') >= 0 || platform.indexOf('ipad') >= 0 || platform.indexOf('ipod') >= 0 || platform.indexOf('iphone') >= 0) && os !== 'Mac' && os !== 'iOS') { 1159 return true 1160 } else { 1161 var platformIsOther = platform.indexOf('win') < 0 && 1162 platform.indexOf('linux') < 0 && 1163 platform.indexOf('mac') < 0 && 1164 platform.indexOf('iphone') < 0 && 1165 platform.indexOf('ipad') < 0 1166 if (platformIsOther !== (os === 'Other')) { 1167 return true 1168 } 1169 } 1170 1171 return typeof navigator.plugins === 'undefined' && os !== 'Windows' && os !== 'Windows Phone' 1172 } 1173 var getHasLiedBrowser = function () { 1174 var userAgent = navigator.userAgent.toLowerCase() 1175 var productSub = navigator.productSub 1176 1177 // we extract the browser from the user agent (respect the order of the tests) 1178 var browser 1179 if (userAgent.indexOf('firefox') >= 0) { 1180 browser = 'Firefox' 1181 } else if (userAgent.indexOf('opera') >= 0 || userAgent.indexOf('opr') >= 0) { 1182 browser = 'Opera' 1183 } else if (userAgent.indexOf('chrome') >= 0) { 1184 browser = 'Chrome' 1185 } else if (userAgent.indexOf('safari') >= 0) { 1186 browser = 'Safari' 1187 } else if (userAgent.indexOf('trident') >= 0) { 1188 browser = 'Internet Explorer' 1189 } else { 1190 browser = 'Other' 1191 } 1192 1193 if ((browser === 'Chrome' || browser === 'Safari' || browser === 'Opera') && productSub !== '20030107') { 1194 return true 1195 } 1196 1197 // eslint-disable-next-line no-eval 1198 var tempRes = eval.toString().length 1199 if (tempRes === 37 && browser !== 'Safari' && browser !== 'Firefox' && browser !== 'Other') { 1200 return true 1201 } else if (tempRes === 39 && browser !== 'Internet Explorer' && browser !== 'Other') { 1202 return true 1203 } else if (tempRes === 33 && browser !== 'Chrome' && browser !== 'Opera' && browser !== 'Other') { 1204 return true 1205 } 1206 1207 // We create an error to see how it is handled 1208 var errFirefox 1209 try { 1210 // eslint-disable-next-line no-throw-literal 1211 throw 'a' 1212 } catch (err) { 1213 try { 1214 err.toSource() 1215 errFirefox = true 1216 } catch (errOfErr) { 1217 errFirefox = false 1218 } 1219 } 1220 return errFirefox && browser !== 'Firefox' && browser !== 'Other' 1221 } 1222 var isCanvasSupported = function () { 1223 var elem = document.createElement('canvas') 1224 return !!(elem.getContext && elem.getContext('2d')) 1225 } 1226 var isWebGlSupported = function () { 1227 // code taken from Modernizr 1228 if (!isCanvasSupported()) { 1229 return false 1230 } 1231 1232 var glContext = getWebglCanvas() 1233 return !!window.WebGLRenderingContext && !!glContext 1234 } 1235 var isIE = function () { 1236 if (navigator.appName === 'Microsoft Internet Explorer') { 1237 return true 1238 } else if (navigator.appName === 'Netscape' && /Trident/.test(navigator.userAgent)) { // IE 11 1239 return true 1240 } 1241 return false 1242 } 1243 var hasSwfObjectLoaded = function () { 1244 return typeof window.swfobject !== 'undefined' 1245 } 1246 var hasMinFlashInstalled = function () { 1247 return window.swfobject.hasFlashPlayerVersion('9.0.0') 1248 } 1249 var addFlashDivNode = function (options) { 1250 var node = document.createElement('div') 1251 node.setAttribute('id', options.fonts.swfContainerId) 1252 document.body.appendChild(node) 1253 } 1254 var loadSwfAndDetectFonts = function (done, options) { 1255 var hiddenCallback = '___fp_swf_loaded' 1256 window[hiddenCallback] = function (fonts) { 1257 done(fonts) 1258 } 1259 var id = options.fonts.swfContainerId 1260 addFlashDivNode() 1261 var flashvars = { onReady: hiddenCallback } 1262 var flashparams = { allowScriptAccess: 'always', menu: 'false' } 1263 window.swfobject.embedSWF(options.fonts.swfPath, id, '1', '1', '9.0.0', false, flashvars, flashparams, {}) 1264 } 1265 var getWebglCanvas = function () { 1266 var canvas = document.createElement('canvas') 1267 var gl = null 1268 try { 1269 gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') 1270 } catch (e) { /* squelch */ } 1271 if (!gl) { gl = null } 1272 return gl 1273 } 1274 1275 var components = [ 1276 { key: 'userAgent', getData: UserAgent }, 1277 { key: 'webdriver', getData: webdriver }, 1278 { key: 'language', getData: languageKey }, 1279 { key: 'colorDepth', getData: colorDepthKey }, 1280 { key: 'deviceMemory', getData: deviceMemoryKey }, 1281 { key: 'pixelRatio', getData: pixelRatioKey }, 1282 { key: 'hardwareConcurrency', getData: hardwareConcurrencyKey }, 1283 { key: 'screenResolution', getData: screenResolutionKey }, 1284 { key: 'availableScreenResolution', getData: availableScreenResolutionKey }, 1285 { key: 'timezoneOffset', getData: timezoneOffset }, 1286 { key: 'timezone', getData: timezone }, 1287 { key: 'sessionStorage', getData: sessionStorageKey }, 1288 { key: 'localStorage', getData: localStorageKey }, 1289 { key: 'indexedDb', getData: indexedDbKey }, 1290 { key: 'addBehavior', getData: addBehaviorKey }, 1291 { key: 'openDatabase', getData: openDatabaseKey }, 1292 { key: 'cpuClass', getData: cpuClassKey }, 1293 { key: 'platform', getData: platformKey }, 1294 { key: 'doNotTrack', getData: doNotTrackKey }, 1295 { key: 'plugins', getData: pluginsComponent }, 1296 { key: 'canvas', getData: canvasKey }, 1297 { key: 'webgl', getData: webglKey }, 1298 { key: 'webglVendorAndRenderer', getData: webglVendorAndRendererKey }, 1299 { key: 'adBlock', getData: adBlockKey }, 1300 { key: 'hasLiedLanguages', getData: hasLiedLanguagesKey }, 1301 { key: 'hasLiedResolution', getData: hasLiedResolutionKey }, 1302 { key: 'hasLiedOs', getData: hasLiedOsKey }, 1303 { key: 'hasLiedBrowser', getData: hasLiedBrowserKey }, 1304 { key: 'touchSupport', getData: touchSupportKey }, 1305 { key: 'fonts', getData: jsFontsKey, pauseBefore: true }, 1306 { key: 'fontsFlash', getData: flashFontsKey, pauseBefore: true }, 1307 { key: 'audio', getData: audioKey }, 1308 { key: 'enumerateDevices', getData: enumerateDevicesKey } 1309 ] 1310 1311 var Fingerprint2 = function (options) { 1312 throw new Error("'new Fingerprint()' is deprecated, see https://github.com/Valve/fingerprintjs2#upgrade-guide-from-182-to-200") 1313 } 1314 1315 Fingerprint2.get = function (options, callback) { 1316 if (!callback) { 1317 callback = options 1318 options = {} 1319 } else if (!options) { 1320 options = {} 1321 } 1322 extendSoft(options, defaultOptions) 1323 options.components = options.extraComponents.concat(components) 1324 1325 var keys = { 1326 data: [], 1327 addPreprocessedComponent: function (key, value) { 1328 if (typeof options.preprocessor === 'function') { 1329 value = options.preprocessor(key, value) 1330 } 1331 keys.data.push({ key: key, value: value }) 1332 } 1333 } 1334 1335 var i = -1 1336 var chainComponents = function (alreadyWaited) { 1337 i += 1 1338 if (i >= options.components.length) { // on finish 1339 callback(keys.data) 1340 return 1341 } 1342 var component = options.components[i] 1343 1344 if (options.excludes[component.key]) { 1345 chainComponents(false) // skip 1346 return 1347 } 1348 1349 if (!alreadyWaited && component.pauseBefore) { 1350 i -= 1 1351 setTimeout(function () { 1352 chainComponents(true) 1353 }, 1) 1354 return 1355 } 1356 1357 try { 1358 component.getData(function (value) { 1359 keys.addPreprocessedComponent(component.key, value) 1360 chainComponents(false) 1361 }, options) 1362 } catch (error) { 1363 // main body error 1364 keys.addPreprocessedComponent(component.key, String(error)) 1365 chainComponents(false) 1366 } 1367 } 1368 1369 chainComponents(false) 1370 } 1371 1372 Fingerprint2.getPromise = function (options) { 1373 return new Promise(function (resolve, reject) { 1374 Fingerprint2.get(options, resolve) 1375 }) 1376 } 1377 1378 Fingerprint2.getV18 = function (options, callback) { 1379 if (callback == null) { 1380 callback = options 1381 options = {} 1382 } 1383 return Fingerprint2.get(options, function (components) { 1384 var newComponents = [] 1385 for (var i = 0; i < components.length; i++) { 1386 var component = components[i] 1387 if (component.value === (options.NOT_AVAILABLE || 'not available')) { 1388 newComponents.push({ key: component.key, value: 'unknown' }) 1389 } else if (component.key === 'plugins') { 1390 newComponents.push({ 1391 key: 'plugins', 1392 value: map(component.value, function (p) { 1393 var mimeTypes = map(p[2], function (mt) { 1394 if (mt.join) { return mt.join('~') } 1395 return mt 1396 }).join(',') 1397 return [p[0], p[1], mimeTypes].join('::') 1398 }) 1399 }) 1400 } else if (['canvas', 'webgl'].indexOf(component.key) !== -1) { 1401 newComponents.push({ key: component.key, value: component.value.join('~') }) 1402 } else if (['sessionStorage', 'localStorage', 'indexedDb', 'addBehavior', 'openDatabase'].indexOf(component.key) !== -1) { 1403 if (component.value) { 1404 newComponents.push({ key: component.key, value: 1 }) 1405 } else { 1406 // skip 1407 continue 1408 } 1409 } else { 1410 if (component.value) { 1411 newComponents.push(component.value.join ? { key: component.key, value: component.value.join(';') } : component) 1412 } else { 1413 newComponents.push({ key: component.key, value: component.value }) 1414 } 1415 } 1416 } 1417 var murmur = x64hash128(map(newComponents, function (component) { return component.value }).join('~~~'), 31) 1418 callback(murmur, newComponents) 1419 }) 1420 } 1421 1422 Fingerprint2.x64hash128 = x64hash128 1423 Fingerprint2.VERSION = '2.1.0' 1424 return Fingerprint2 1425 })