fingerprintjs2
Version:
Modern & flexible browser fingerprinting library
423 lines (386 loc) • 16.8 kB
JavaScript
/* global jasmine, describe, it, xit , expect, Fingerprint2 */
function getComponent (components, key) {
for (var x = 0; x < components.length; x++) {
if (components[x].key === key) {
return components[x].value
}
}
throw new Error('Component not found: ' + key)
}
var onPhantomJs = window._phantom != null
describe('Fingerprint2', function () {
describe('FP2', function () {
it('.get should be available', function () {
expect(Fingerprint2.get).toBeDefined()
})
it('.getPromise should be available', function () {
expect(Fingerprint2.getPromise).toBeDefined()
})
it('.get accepts an empty options object', function (done) {
Fingerprint2.get({}, function (result) {
expect(result).not.toBeNull()
done()
})
})
it('.get works when omitting options object', function (done) {
Fingerprint2.get(function (result) {
expect(result).not.toBeNull()
done()
})
})
})
// legacy usage
describe('new', function () {
it('throws an error', function () {
// eslint-disable-next-line
expect(function () { new Fingerprint2() }).toThrow()
})
})
// legacy
describe('without new keyword', function () {
it('throws an error', function () {
expect(function () { Fingerprint2() }).toThrow()
})
})
describe('get', function () {
describe('default options', function () {
// legacy
it('calculates fingerprint hash', function (done) {
Fingerprint2.getV18(function (result) {
expect(result).toMatch(/^[0-9a-f]{32}$/i)
done()
})
})
it('does not try calling flash font detection', function (done) {
var flashFontKey = 'fontsFlash'
Fingerprint2.get(function (components) {
expect(components.some(function (componentResult) {
return componentResult.key === flashFontKey
})).toBeFalse()
done()
})
})
})
describe('excludes work:', function () {
[
'userAgent',
'pixelRatio',
'deviceMemory',
'screenResolution',
'availableScreenResolution',
'plugins',
'timezone'
].forEach(function (key) {
it('does not use ' + key + ' when excluded', function (done) {
var options = {excludes: {}}
options.excludes[key] = true
Fingerprint2.get(options, function (components) {
expect(components.some(function (componentResult) {
return componentResult.key === key
})).toBeFalse()
done()
})
})
})
})
// navigator.appName is read only
xit('does not use IE plugins info when excluded', function (done) {
var previous = navigator.appName
navigator.appName = 'Microsoft Internet Explorer'
var key = 'plugins'
Fingerprint2.get({plugins: {excludeIE: true}}, function (components) {
expect(components.some(function (componentResult) {
return componentResult.key === key
})).toBeFalse()
navigator.appName = previous
done()
})
})
describe('returns components', function () {
it('does it return components as a second argument to callback', function (done) {
Fingerprint2.get(function (components) {
expect(components).not.toBeNull()
done()
})
})
it('checks if returned components is array', function (done) {
Fingerprint2.get(function (components) {
expect(components).toBeArrayOfObjects()
done()
})
})
it('checks if fonts component is array', function (done) {
Fingerprint2.get(function (components) {
expect(getComponent(components, 'fonts')).toBeArray()
done()
})
})
it('returns userAgent as the first element', function (done) {
Fingerprint2.get(function (components) {
expect(components[0].key).toEqual('userAgent')
done()
})
})
})
describe('baseFontArray iteration', function () {
it('only iterates specified items', function (done) {
var baseFonts = ['monospace', 'sans-serif', 'serif']
// eslint-disable-next-line
var ctr = 0; for (var _ in baseFonts) { ctr++ }
expect(baseFonts.length).toEqual(3)
expect(ctr).toEqual(baseFonts.length)
// Somewhere deep in your JavaScript library...
// eslint-disable-next-line
Array.prototype.foo = 1
// eslint-disable-next-line
Array.prototype.bar = 2
// eslint-disable-next-line
ctr = 0; for (var _ in baseFonts) {
ctr++
// Now foo & bar is a part of EVERY array and
// will show up here as a value of 'x'.
}
expect(baseFonts.length).toEqual(3)
// sadface
expect(ctr).not.toEqual(baseFonts.length)
expect(ctr).toEqual(5)
done()
})
})
describe('userDefinedFonts option', function () {
it('concatinates existing fonts with user-defined', function (done) {
var fontList = [
'Andale Mono', 'Arial', 'Arial Black', 'Arial Hebrew', 'Arial MT', 'Arial Narrow', 'Arial Rounded MT Bold',
'Arial Unicode MS',
'Bitstream Vera Sans Mono', 'Book Antiqua', 'Bookman Old Style',
'Calibri', 'Cambria', 'Cambria Math', 'Century', 'Century Gothic', 'Century Schoolbook', 'Comic Sans',
'Comic Sans MS', 'Consolas', 'Courier', 'Courier New',
'Garamond', 'Geneva', 'Georgia',
'Helvetica', 'Helvetica Neue',
'Impact',
'Lucida Bright', 'Lucida Calligraphy', 'Lucida Console', 'Lucida Fax', 'LUCIDA GRANDE', 'Lucida Handwriting',
'Lucida Sans', 'Lucida Sans Typewriter', 'Lucida Sans Unicode',
'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS Outlook', 'MS PGothic',
'MS Reference Sans Serif', 'MS Sans Serif', 'MS Serif', 'MYRIAD', 'MYRIAD PRO',
'Palatino', 'Palatino Linotype',
'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Light', 'Segoe UI Semibold', 'Segoe UI Symbol',
'Tahoma', 'Times', 'Times New Roman', 'Times New Roman PS', 'Trebuchet MS',
'Verdana', 'Wingdings', 'Wingdings 2', 'Wingdings 3'
]
expect(fontList.length).toEqual(65)
var userDefinedFonts = []
fontList.concat(userDefinedFonts)
expect(fontList.length).toEqual(65)
userDefinedFonts = ['Adria Grotesk', 'Butler', 'Nimbus Mono', 'Arial', 'Nimbus Mono']
expect(userDefinedFonts.length).toEqual(5)
fontList = fontList.concat(userDefinedFonts)
// remove duplicate fonts: "Arial" from default fonts & duplicate "Nimbus Mono" from `userDefinedFonts` variable.
fontList = fontList.filter(function (font, position) {
return fontList.indexOf(font) === position
})
expect(fontList.length).toEqual(65 + 5 - 2)
done()
})
})
describe('extraComponents option', function () {
it('uses extraComponents', function (done) {
function customFunction (done) {
done('RANDOM_STRING')
}
var spy = jasmine.createSpy('customFunction', customFunction).and.callThrough()
Fingerprint2.get({
extraComponents: [{key: 'customFunction', getData: spy}]
}, function (keys) {
expect(spy).toHaveBeenCalled()
done()
})
})
it('its key is used', function (done) {
var options = {
extraComponents: [
{
key: 'TEST_STRING',
getData: function customFunction (done) {
done('RANDOM_STRING')
}
}
]
}
Fingerprint2.get(options, function (components) {
expect(getComponent(components, 'TEST_STRING')).toEqual('RANDOM_STRING')
done()
})
})
it('safely introduce a new component even if it throws', function (done) {
var options = {
extraComponents: [
{
key: 'my key',
getData: function customFunction (done) {
throw new Error('unstable component')
}
}
]
}
Fingerprint2.get(options, function (components) {
// if we arrive here, it means the rest of the fingerprint was not disturbed
expect(components).toBeDefined()
done()
})
})
})
describe('error constants', function () {
it('are configurable', function (done) {
var NA = 'NA'
var options = {
NOT_AVAILABLE: NA,
extraComponents: [
{
key: 'my key',
getData: function customFunction (done, options2) {
done(options2.NOT_AVAILABLE)
}
}
]
}
Fingerprint2.get(options, function (components) {
expect(components).toBeDefined()
expect(getComponent(components, 'my key')).toEqual(NA)
done()
})
})
})
if (!onPhantomJs) {
describe('enumerate devices fingerprint', function () {
it('checks enumerate devices fingerprint', function (done) {
var options = {excludes: {}}
Fingerprint2.get(options, function (components) {
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
expect(getComponent(components, 'enumerateDevices')).not.toBeNull()
}
done()
})
})
})
}
if (!onPhantomJs) {
describe('audio fingerprint', function () {
it('checks audio fingerprint', function (done) {
Fingerprint2.get(function (components) {
expect(getComponent(components, 'audio')).not.toBeNull()
done()
})
})
})
}
if (!onPhantomJs) {
describe('webgl shader precision format', function () {
// fp2.getWebglCanvas() not exposed
it('checks webgl shader precision format loop', function (done) {
Fingerprint2.get(function (components) {
getComponent(components, 'webgl')
})
var canvas = document.createElement('canvas')
var gl = null
try {
gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
} catch (e) { /* squelch */ }
if (gl == null) {
done()
}
var item = function (name, descr, attr1, attr2, attr3) {
var fmt = gl.getShaderPrecisionFormat(attr1, attr2)[attr3]
return ['webgl ', name, ' shader ', descr, ':', fmt].join('')
}
var webglExpectedArray = [ /* eslint-disable */
item('vertex' , 'high float precision' , gl.VERTEX_SHADER , gl.HIGH_FLOAT , 'precision'),
item('vertex' , 'high float precision rangeMin' , gl.VERTEX_SHADER , gl.HIGH_FLOAT , 'rangeMin'),
item('vertex' , 'high float precision rangeMax' , gl.VERTEX_SHADER , gl.HIGH_FLOAT , 'rangeMax'),
item('vertex' , 'medium float precision' , gl.VERTEX_SHADER , gl.MEDIUM_FLOAT, 'precision'),
item('vertex' , 'medium float precision rangeMin', gl.VERTEX_SHADER , gl.MEDIUM_FLOAT, 'rangeMin'),
item('vertex' , 'medium float precision rangeMax', gl.VERTEX_SHADER , gl.MEDIUM_FLOAT, 'rangeMax'),
item('vertex' , 'low float precision' , gl.VERTEX_SHADER , gl.LOW_FLOAT , 'precision'),
item('vertex' , 'low float precision rangeMin' , gl.VERTEX_SHADER , gl.LOW_FLOAT , 'rangeMin'),
item('vertex' , 'low float precision rangeMax' , gl.VERTEX_SHADER , gl.LOW_FLOAT , 'rangeMax'),
item('vertex' , 'high int precision' , gl.VERTEX_SHADER , gl.HIGH_INT , 'precision'),
item('vertex' , 'high int precision rangeMin' , gl.VERTEX_SHADER , gl.HIGH_INT , 'rangeMin'),
item('vertex' , 'high int precision rangeMax' , gl.VERTEX_SHADER , gl.HIGH_INT , 'rangeMax'),
item('vertex' , 'medium int precision' , gl.VERTEX_SHADER , gl.MEDIUM_INT , 'precision'),
item('vertex' , 'medium int precision rangeMin' , gl.VERTEX_SHADER , gl.MEDIUM_INT , 'rangeMin'),
item('vertex' , 'medium int precision rangeMax' , gl.VERTEX_SHADER , gl.MEDIUM_INT , 'rangeMax'),
item('vertex' , 'low int precision' , gl.VERTEX_SHADER , gl.LOW_INT , 'precision'),
item('vertex' , 'low int precision rangeMin' , gl.VERTEX_SHADER , gl.LOW_INT , 'rangeMin'),
item('vertex' , 'low int precision rangeMax' , gl.VERTEX_SHADER , gl.LOW_INT , 'rangeMax'),
item('fragment', 'high float precision' , gl.FRAGMENT_SHADER, gl.HIGH_FLOAT , 'precision'),
item('fragment', 'high float precision rangeMin' , gl.FRAGMENT_SHADER, gl.HIGH_FLOAT , 'rangeMin'),
item('fragment', 'high float precision rangeMax' , gl.FRAGMENT_SHADER, gl.HIGH_FLOAT , 'rangeMax'),
item('fragment', 'medium float precision' , gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT, 'precision'),
item('fragment', 'medium float precision rangeMin', gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT, 'rangeMin'),
item('fragment', 'medium float precision rangeMax', gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT, 'rangeMax'),
item('fragment', 'low float precision' , gl.FRAGMENT_SHADER, gl.LOW_FLOAT , 'precision'),
item('fragment', 'low float precision rangeMin' , gl.FRAGMENT_SHADER, gl.LOW_FLOAT , 'rangeMin'),
item('fragment', 'low float precision rangeMax' , gl.FRAGMENT_SHADER, gl.LOW_FLOAT , 'rangeMax'),
item('fragment', 'high int precision' , gl.FRAGMENT_SHADER, gl.HIGH_INT , 'precision'),
item('fragment', 'high int precision rangeMin' , gl.FRAGMENT_SHADER, gl.HIGH_INT , 'rangeMin'),
item('fragment', 'high int precision rangeMax' , gl.FRAGMENT_SHADER, gl.HIGH_INT , 'rangeMax'),
item('fragment', 'medium int precision' , gl.FRAGMENT_SHADER, gl.MEDIUM_INT , 'precision'),
item('fragment', 'medium int precision rangeMin' , gl.FRAGMENT_SHADER, gl.MEDIUM_INT , 'rangeMin'),
item('fragment', 'medium int precision rangeMax' , gl.FRAGMENT_SHADER, gl.MEDIUM_INT , 'rangeMax'),
item('fragment', 'low int precision' , gl.FRAGMENT_SHADER, gl.LOW_INT , 'precision'),
item('fragment', 'low int precision rangeMin' , gl.FRAGMENT_SHADER, gl.LOW_INT , 'rangeMin'),
item('fragment', 'low int precision rangeMax' , gl.FRAGMENT_SHADER, gl.LOW_INT , 'rangeMax'),
]
Fingerprint2.get(function (components) {
webglExpectedArray.forEach(function (item) {
expect(getComponent(components, 'webgl').indexOf(item)).not.toEqual(-1)
})
done()
})
})
})
}
describe('preprocessor', function () {
it('checks that preprocessor not used by default', function (done) {
Fingerprint2.get(function (components) {
expect(getComponent(components, 'userAgent')).not.toEqual('MyUserAgent')
done()
})
})
it('checks that preprocessor function applied to component value', function (done) {
var mykey = 'userAgent'
var options = {
preprocessor: function (key, value) {
if (key === mykey) {
return 'MyUserAgent'
}
return value
}
}
Fingerprint2.get(options, function (components) {
expect(getComponent(components, mykey)).toEqual('MyUserAgent')
done()
})
})
})
if (!onPhantomJs) {
describe('extra', function () {
//https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/navigator/NavigatorPlugins-impl.js
it('should no throw in jsdom like environment', function (done) {
// direct assignment does not work
Object.defineProperty(navigator, "plugins", {
value: {
javaEnabled: function () {
return false
}
}
})
Fingerprint2.get(function (components) {
expect(getComponent(components, 'plugins')).toBeDefined()
done()
})
})
})
}
})
})