248 lines
7.0 KiB
JavaScript
248 lines
7.0 KiB
JavaScript
const getBrowserFingerprint = ({ hardwareOnly = false, enableWebgl = false, debug = false } = {}) => {
|
|
const devicePixelRatio = +parseInt(window.devicePixelRatio);
|
|
|
|
const {
|
|
appName,
|
|
appCodeName,
|
|
appVersion,
|
|
cookieEnabled,
|
|
deviceMemory,
|
|
doNotTrack,
|
|
hardwareConcurrency,
|
|
language,
|
|
languages,
|
|
maxTouchPoints,
|
|
platform,
|
|
product,
|
|
productSub,
|
|
userAgent,
|
|
vendor,
|
|
vendorSub,
|
|
} = window.navigator;
|
|
|
|
const { width, height, colorDepth, pixelDepth } = window.screen;
|
|
const timezoneOffset = new Date().getTimezoneOffset();
|
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
const touchSupport = 'ontouchstart' in window;
|
|
|
|
const canvas = getCanvasID(debug);
|
|
const webgl = enableWebgl ? getWebglID(debug) : undefined; // undefined will remove this from the stringify down here
|
|
const webglInfo = enableWebgl ? getWebglInfo(debug) : undefined; // undefined will remove this from the stringify down here
|
|
|
|
const data = hardwareOnly
|
|
? JSON.stringify({
|
|
canvas,
|
|
colorDepth,
|
|
deviceMemory,
|
|
devicePixelRatio,
|
|
hardwareConcurrency,
|
|
height,
|
|
maxTouchPoints,
|
|
pixelDepth,
|
|
platform,
|
|
touchSupport,
|
|
webgl,
|
|
webglInfo,
|
|
width,
|
|
})
|
|
: JSON.stringify({
|
|
appCodeName,
|
|
appName,
|
|
appVersion,
|
|
canvas,
|
|
colorDepth,
|
|
cookieEnabled,
|
|
deviceMemory,
|
|
devicePixelRatio,
|
|
doNotTrack,
|
|
hardwareConcurrency,
|
|
height,
|
|
language,
|
|
languages,
|
|
maxTouchPoints,
|
|
pixelDepth,
|
|
platform,
|
|
product,
|
|
productSub,
|
|
timezone,
|
|
timezoneOffset,
|
|
touchSupport,
|
|
userAgent,
|
|
vendor,
|
|
vendorSub,
|
|
webgl,
|
|
webglInfo,
|
|
width,
|
|
});
|
|
|
|
const datastring = JSON.stringify(data, null, 4);
|
|
|
|
if (debug) console.log('fingerprint data', datastring);
|
|
|
|
const result = murmurhash3_32_gc(datastring);
|
|
return result;
|
|
};
|
|
|
|
export const getCanvasID = (debug) => {
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~1!2@3#4$5%6^7&8*9(0)-_=+[{]}|;:',<.>/?";
|
|
ctx.textBaseline = 'top';
|
|
ctx.font = "14px 'Arial'";
|
|
ctx.textBaseline = 'alphabetic';
|
|
ctx.fillStyle = '#f60';
|
|
ctx.fillRect(125, 1, 62, 20);
|
|
ctx.fillStyle = '#069';
|
|
ctx.fillText(text, 2, 15);
|
|
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
|
|
ctx.fillText(text, 4, 17);
|
|
|
|
const result = canvas.toDataURL();
|
|
|
|
if (debug) {
|
|
document.body.appendChild(canvas);
|
|
} else {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
|
|
return murmurhash3_32_gc(result);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const getWebglID = (debug) => {
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('webgl');
|
|
canvas.width = 256;
|
|
canvas.height = 128;
|
|
|
|
const f =
|
|
'attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}';
|
|
const g = 'precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}';
|
|
const h = ctx.createBuffer();
|
|
|
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, h);
|
|
|
|
const i = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.7321, 0]);
|
|
|
|
ctx.bufferData(ctx.ARRAY_BUFFER, i, ctx.STATIC_DRAW), (h.itemSize = 3), (h.numItems = 3);
|
|
|
|
const j = ctx.createProgram();
|
|
const k = ctx.createShader(ctx.VERTEX_SHADER);
|
|
|
|
ctx.shaderSource(k, f);
|
|
ctx.compileShader(k);
|
|
|
|
const l = ctx.createShader(ctx.FRAGMENT_SHADER);
|
|
|
|
ctx.shaderSource(l, g);
|
|
ctx.compileShader(l);
|
|
ctx.attachShader(j, k);
|
|
ctx.attachShader(j, l);
|
|
ctx.linkProgram(j);
|
|
ctx.useProgram(j);
|
|
|
|
j.vertexPosAttrib = ctx.getAttribLocation(j, 'attrVertex');
|
|
j.offsetUniform = ctx.getUniformLocation(j, 'uniformOffset');
|
|
|
|
ctx.enableVertexAttribArray(j.vertexPosArray);
|
|
ctx.vertexAttribPointer(j.vertexPosAttrib, h.itemSize, ctx.FLOAT, !1, 0, 0);
|
|
ctx.uniform2f(j.offsetUniform, 1, 1);
|
|
ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, h.numItems);
|
|
|
|
const n = new Uint8Array(canvas.width * canvas.height * 4);
|
|
ctx.readPixels(0, 0, canvas.width, canvas.height, ctx.RGBA, ctx.UNSIGNED_BYTE, n);
|
|
|
|
const result = JSON.stringify(n).replace(/,?"[0-9]+":/g, '');
|
|
|
|
if (debug) {
|
|
document.body.appendChild(canvas);
|
|
} else {
|
|
ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
|
|
}
|
|
|
|
return murmurhash3_32_gc(result);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const getWebglInfo = () => {
|
|
try {
|
|
const ctx = document.createElement('canvas').getContext('webgl');
|
|
|
|
const result = {
|
|
VERSION: ctx.getParameter(ctx.VERSION),
|
|
SHADING_LANGUAGE_VERSION: ctx.getParameter(ctx.SHADING_LANGUAGE_VERSION),
|
|
VENDOR: ctx.getParameter(ctx.VENDOR),
|
|
SUPORTED_EXTENSIONS: ctx.getSupportedExtensions(),
|
|
};
|
|
|
|
return result;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const murmurhash3_32_gc = (key) => {
|
|
const remainder = key.length & 3; // key.length % 4
|
|
const bytes = key.length - remainder;
|
|
const c1 = 0xcc9e2d51;
|
|
const c2 = 0x1b873593;
|
|
|
|
let h1, h1b, k1;
|
|
|
|
for (let i = 0; i < bytes; i++) {
|
|
k1 = (key.charCodeAt(i) & 0xff) | ((key.charCodeAt(++i) & 0xff) << 8) | ((key.charCodeAt(++i) & 0xff) << 16) | ((key.charCodeAt(++i) & 0xff) << 24);
|
|
++i;
|
|
|
|
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
k1 = (k1 << 15) | (k1 >>> 17);
|
|
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
|
|
h1 ^= k1;
|
|
h1 = (h1 << 13) | (h1 >>> 19);
|
|
h1b = ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
|
|
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
|
|
}
|
|
|
|
const i = bytes - 1;
|
|
|
|
k1 = 0;
|
|
|
|
switch (remainder) {
|
|
case 3: {
|
|
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
|
|
break;
|
|
}
|
|
case 2: {
|
|
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
|
|
break;
|
|
}
|
|
case 1: {
|
|
k1 ^= key.charCodeAt(i) & 0xff;
|
|
break;
|
|
}
|
|
}
|
|
|
|
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
|
|
k1 = (k1 << 15) | (k1 >>> 17);
|
|
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
|
|
h1 ^= k1;
|
|
|
|
h1 ^= key.length;
|
|
|
|
h1 ^= h1 >>> 16;
|
|
h1 = ((h1 & 0xffff) * 0x85ebca6b + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
|
|
h1 ^= h1 >>> 13;
|
|
h1 = ((h1 & 0xffff) * 0xc2b2ae35 + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) & 0xffffffff;
|
|
h1 ^= h1 >>> 16;
|
|
|
|
return h1 >>> 0;
|
|
};
|
|
|
|
export default getBrowserFingerprint;
|