Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/modules/browser/fingerprint_browser/fingerprint2.js
1154 views
1
/*
2
* Fingerprintjs2 2.0.6 - Modern & flexible browser fingerprint library v2
3
* https://github.com/Valve/fingerprintjs2
4
* Copyright (c) 2015 Valentin Vasilyev ([email protected])
5
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6
*
7
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
8
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
9
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
10
* ARE DISCLAIMED. IN NO EVENT SHALL VALENTIN VASILYEV BE LIABLE FOR ANY
11
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
12
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
13
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
14
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
15
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
16
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17
*/
18
/* global define */
19
(function (name, context, definition) {
20
'use strict'
21
if (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() }
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.charCodeAt(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 Condensed', '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))
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.6'
1410
return Fingerprint2
1411
})
1412
1413