fpscanner 0.2.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +639 -55
- package/bin/cli.js +216 -0
- package/dist/crypto-helpers.d.ts +19 -0
- package/dist/crypto-helpers.d.ts.map +1 -0
- package/dist/detections/hasCDP.d.ts +3 -0
- package/dist/detections/hasCDP.d.ts.map +1 -0
- package/dist/detections/hasContextMismatch.d.ts +3 -0
- package/dist/detections/hasContextMismatch.d.ts.map +1 -0
- package/dist/detections/hasHeadlessChromeScreenResolution.d.ts +3 -0
- package/dist/detections/hasHeadlessChromeScreenResolution.d.ts.map +1 -0
- package/dist/detections/hasHighCPUCount.d.ts +3 -0
- package/dist/detections/hasHighCPUCount.d.ts.map +1 -0
- package/dist/detections/hasImpossibleDeviceMemory.d.ts +3 -0
- package/dist/detections/hasImpossibleDeviceMemory.d.ts.map +1 -0
- package/dist/detections/hasMismatchPlatformIframe.d.ts +3 -0
- package/dist/detections/hasMismatchPlatformIframe.d.ts.map +1 -0
- package/dist/detections/hasMismatchPlatformWorker.d.ts +3 -0
- package/dist/detections/hasMismatchPlatformWorker.d.ts.map +1 -0
- package/dist/detections/hasMismatchWebGLInWorker.d.ts +3 -0
- package/dist/detections/hasMismatchWebGLInWorker.d.ts.map +1 -0
- package/dist/detections/hasMissingChromeObject.d.ts +3 -0
- package/dist/detections/hasMissingChromeObject.d.ts.map +1 -0
- package/dist/detections/hasPlaywright.d.ts +3 -0
- package/dist/detections/hasPlaywright.d.ts.map +1 -0
- package/dist/detections/hasSeleniumProperty.d.ts +3 -0
- package/dist/detections/hasSeleniumProperty.d.ts.map +1 -0
- package/dist/detections/hasSwiftshaderRenderer.d.ts +3 -0
- package/dist/detections/hasSwiftshaderRenderer.d.ts.map +1 -0
- package/dist/detections/hasUTCTimezone.d.ts +3 -0
- package/dist/detections/hasUTCTimezone.d.ts.map +1 -0
- package/dist/detections/hasWebdriver.d.ts +3 -0
- package/dist/detections/hasWebdriver.d.ts.map +1 -0
- package/dist/detections/hasWebdriverIframe.d.ts +3 -0
- package/dist/detections/hasWebdriverIframe.d.ts.map +1 -0
- package/dist/detections/hasWebdriverWorker.d.ts +3 -0
- package/dist/detections/hasWebdriverWorker.d.ts.map +1 -0
- package/dist/detections/hasWebdriverWritable.d.ts +3 -0
- package/dist/detections/hasWebdriverWritable.d.ts.map +1 -0
- package/dist/fpScanner.cjs.js +31 -0
- package/dist/fpScanner.es.js +1066 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/signals/browserExtensions.d.ts +5 -0
- package/dist/signals/browserExtensions.d.ts.map +1 -0
- package/dist/signals/browserFeatures.d.ts +14 -0
- package/dist/signals/browserFeatures.d.ts.map +1 -0
- package/dist/signals/canvas.d.ts +6 -0
- package/dist/signals/canvas.d.ts.map +1 -0
- package/dist/signals/cdp.d.ts +2 -0
- package/dist/signals/cdp.d.ts.map +1 -0
- package/dist/signals/cpuCount.d.ts +2 -0
- package/dist/signals/cpuCount.d.ts.map +1 -0
- package/dist/signals/etsl.d.ts +2 -0
- package/dist/signals/etsl.d.ts.map +1 -0
- package/dist/signals/highEntropyValues.d.ts +11 -0
- package/dist/signals/highEntropyValues.d.ts.map +1 -0
- package/dist/signals/iframe.d.ts +9 -0
- package/dist/signals/iframe.d.ts.map +1 -0
- package/dist/signals/internationalization.d.ts +5 -0
- package/dist/signals/internationalization.d.ts.map +1 -0
- package/dist/signals/languages.d.ts +5 -0
- package/dist/signals/languages.d.ts.map +1 -0
- package/dist/signals/maths.d.ts +2 -0
- package/dist/signals/maths.d.ts.map +1 -0
- package/dist/signals/mediaCodecs.d.ts +11 -0
- package/dist/signals/mediaCodecs.d.ts.map +1 -0
- package/dist/signals/mediaQueries.d.ts +13 -0
- package/dist/signals/mediaQueries.d.ts.map +1 -0
- package/dist/signals/memory.d.ts +2 -0
- package/dist/signals/memory.d.ts.map +1 -0
- package/dist/signals/multimediaDevices.d.ts +2 -0
- package/dist/signals/multimediaDevices.d.ts.map +1 -0
- package/dist/signals/navigatorPropertyDescriptors.d.ts +2 -0
- package/dist/signals/navigatorPropertyDescriptors.d.ts.map +1 -0
- package/dist/signals/nonce.d.ts +2 -0
- package/dist/signals/nonce.d.ts.map +1 -0
- package/dist/signals/platform.d.ts +2 -0
- package/dist/signals/platform.d.ts.map +1 -0
- package/dist/signals/playwright.d.ts +2 -0
- package/dist/signals/playwright.d.ts.map +1 -0
- package/dist/signals/plugins.d.ts +9 -0
- package/dist/signals/plugins.d.ts.map +1 -0
- package/dist/signals/screenResolution.d.ts +12 -0
- package/dist/signals/screenResolution.d.ts.map +1 -0
- package/dist/signals/seleniumProperties.d.ts +2 -0
- package/dist/signals/seleniumProperties.d.ts.map +1 -0
- package/dist/signals/time.d.ts +2 -0
- package/dist/signals/time.d.ts.map +1 -0
- package/dist/signals/toSourceError.d.ts +5 -0
- package/dist/signals/toSourceError.d.ts.map +1 -0
- package/dist/signals/url.d.ts +2 -0
- package/dist/signals/url.d.ts.map +1 -0
- package/dist/signals/userAgent.d.ts +2 -0
- package/dist/signals/userAgent.d.ts.map +1 -0
- package/dist/signals/utils.d.ts +11 -0
- package/dist/signals/utils.d.ts.map +1 -0
- package/dist/signals/webGL.d.ts +5 -0
- package/dist/signals/webGL.d.ts.map +1 -0
- package/dist/signals/webdriver.d.ts +2 -0
- package/dist/signals/webdriver.d.ts.map +1 -0
- package/dist/signals/webdriverWritable.d.ts +2 -0
- package/dist/signals/webdriverWritable.d.ts.map +1 -0
- package/dist/signals/webgpu.d.ts +7 -0
- package/dist/signals/webgpu.d.ts.map +1 -0
- package/dist/signals/worker.d.ts +2 -0
- package/dist/signals/worker.d.ts.map +1 -0
- package/dist/types.d.ts +207 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +58 -15
- package/scripts/build-custom.js +246 -0
- package/src/crypto-helpers.ts +50 -0
- package/src/detections/hasCDP.ts +5 -0
- package/src/detections/hasContextMismatch.ts +19 -0
- package/src/detections/hasHeadlessChromeScreenResolution.ts +10 -0
- package/src/detections/hasHighCPUCount.ts +9 -0
- package/src/detections/hasImpossibleDeviceMemory.ts +9 -0
- package/src/detections/hasMismatchPlatformIframe.ts +10 -0
- package/src/detections/hasMismatchPlatformWorker.ts +10 -0
- package/src/detections/hasMismatchWebGLInWorker.ts +13 -0
- package/src/detections/hasMissingChromeObject.ts +6 -0
- package/src/detections/hasPlaywright.ts +5 -0
- package/src/detections/hasSeleniumProperty.ts +5 -0
- package/src/detections/hasSwiftshaderRenderer.ts +5 -0
- package/src/detections/hasUTCTimezone.ts +5 -0
- package/src/detections/hasWebdriver.ts +5 -0
- package/src/detections/hasWebdriverIframe.ts +5 -0
- package/src/detections/hasWebdriverWorker.ts +5 -0
- package/src/detections/hasWebdriverWritable.ts +5 -0
- package/src/globals.d.ts +10 -0
- package/src/index.ts +644 -0
- package/src/signals/browserExtensions.ts +57 -0
- package/src/signals/browserFeatures.ts +24 -0
- package/src/signals/canvas.ts +84 -0
- package/src/signals/cdp.ts +18 -0
- package/src/signals/cpuCount.ts +5 -0
- package/src/signals/etsl.ts +3 -0
- package/src/signals/highEntropyValues.ts +48 -0
- package/src/signals/iframe.ts +34 -0
- package/src/signals/internationalization.ts +24 -0
- package/src/signals/languages.ts +6 -0
- package/src/signals/maths.ts +30 -0
- package/src/signals/mediaCodecs.ts +120 -0
- package/src/signals/mediaQueries.ts +85 -0
- package/src/signals/memory.ts +5 -0
- package/src/signals/multimediaDevices.ts +34 -0
- package/src/signals/navigatorPropertyDescriptors.ts +17 -0
- package/src/signals/nonce.ts +3 -0
- package/src/signals/platform.ts +3 -0
- package/src/signals/playwright.ts +3 -0
- package/src/signals/plugins.ts +70 -0
- package/src/signals/screenResolution.ts +15 -0
- package/src/signals/seleniumProperties.ts +40 -0
- package/src/signals/time.ts +3 -0
- package/src/signals/toSourceError.ts +27 -0
- package/src/signals/url.ts +3 -0
- package/src/signals/userAgent.ts +3 -0
- package/src/signals/utils.ts +29 -0
- package/src/signals/webGL.ts +28 -0
- package/src/signals/webdriver.ts +3 -0
- package/src/signals/webdriverWritable.ts +15 -0
- package/src/signals/webgpu.ts +28 -0
- package/src/signals/worker.ts +77 -0
- package/src/types.ts +237 -0
- package/.babelrc +0 -3
- package/.travis.yml +0 -17
- package/src/fpScanner.js +0 -222
- package/test/test.html +0 -11
- package/test/test.js +0 -116
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { INIT } from "./utils";
|
|
2
|
+
|
|
3
|
+
export function browserExtensions() {
|
|
4
|
+
const browserExtensionsData = {
|
|
5
|
+
bitmask: INIT,
|
|
6
|
+
extensions: [] as string[],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const hasGrammarly = document.body.hasAttribute('data-gr-ext-installed');
|
|
10
|
+
const hasMetamask = typeof (window as any).ethereum !=='undefined';
|
|
11
|
+
const hasCouponBirds = document.getElementById('coupon-birds-drop-div') !== null;
|
|
12
|
+
const hasDeepL = document.querySelector('deepl-input-controller') !== null;
|
|
13
|
+
const hasMonicaAI = document.getElementById('monica-content-root') !== null;
|
|
14
|
+
const hasSiderAI = document.querySelector('chatgpt-sidebar') !== null;
|
|
15
|
+
const hasRequestly = typeof (window as any).__REQUESTLY__ !== 'undefined';
|
|
16
|
+
const hasVeepn = Array.from(document.querySelectorAll('*'))
|
|
17
|
+
.filter(el => el.tagName.toLowerCase().startsWith('veepn-')).length > 0;
|
|
18
|
+
|
|
19
|
+
browserExtensionsData.bitmask = [
|
|
20
|
+
hasGrammarly ? '1' : '0',
|
|
21
|
+
hasMetamask ? '1' : '0',
|
|
22
|
+
hasCouponBirds ? '1' : '0',
|
|
23
|
+
hasDeepL ? '1' : '0',
|
|
24
|
+
hasMonicaAI ? '1' : '0',
|
|
25
|
+
hasSiderAI ? '1' : '0',
|
|
26
|
+
hasRequestly ? '1' : '0',
|
|
27
|
+
hasVeepn ? '1' : '0',
|
|
28
|
+
].join('');
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if (hasGrammarly) {
|
|
32
|
+
browserExtensionsData.extensions.push('grammarly');
|
|
33
|
+
}
|
|
34
|
+
if (hasMetamask) {
|
|
35
|
+
browserExtensionsData.extensions.push('metamask');
|
|
36
|
+
}
|
|
37
|
+
if (hasCouponBirds) {
|
|
38
|
+
browserExtensionsData.extensions.push('coupon-birds');
|
|
39
|
+
}
|
|
40
|
+
if (hasDeepL) {
|
|
41
|
+
browserExtensionsData.extensions.push('deepl');
|
|
42
|
+
}
|
|
43
|
+
if (hasMonicaAI) {
|
|
44
|
+
browserExtensionsData.extensions.push('monica-ai');
|
|
45
|
+
}
|
|
46
|
+
if (hasSiderAI) {
|
|
47
|
+
browserExtensionsData.extensions.push('sider-ai');
|
|
48
|
+
}
|
|
49
|
+
if (hasRequestly) {
|
|
50
|
+
browserExtensionsData.extensions.push('requestly');
|
|
51
|
+
}
|
|
52
|
+
if (hasVeepn) {
|
|
53
|
+
browserExtensionsData.extensions.push('veepn');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return browserExtensionsData;
|
|
57
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { INIT } from "./utils";
|
|
2
|
+
|
|
3
|
+
export function browserFeatures() {
|
|
4
|
+
const browserFeaturesData = {
|
|
5
|
+
bitmask: INIT,
|
|
6
|
+
chrome: 'chrome' in window,
|
|
7
|
+
brave: 'brave' in navigator,
|
|
8
|
+
applePaySupport: 'ApplePaySetup' in window,
|
|
9
|
+
opera: (typeof (window as any).opr !== "undefined") ||
|
|
10
|
+
(typeof (window as any).onoperadetachedviewchange === "object"),
|
|
11
|
+
serial: (window.navigator as any).serial !== undefined,
|
|
12
|
+
attachShadow: !!Element.prototype.attachShadow,
|
|
13
|
+
caches: !!window.caches,
|
|
14
|
+
webAssembly: !!window.WebAssembly && !!window.WebAssembly.instantiate,
|
|
15
|
+
buffer: 'Buffer' in window,
|
|
16
|
+
showModalDialog: 'showModalDialog' in window,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// set bitmask to 0/1 string based on browserFeaturesData, exclude bitmask property itself (you need to filter on the key)
|
|
20
|
+
// use the filter function to exclude the bitmask property itself
|
|
21
|
+
const bitmask = Object.keys(browserFeaturesData).filter((key) => key !== 'bitmask').map(key => (browserFeaturesData as any)[key] ? '1' : '0').join('');
|
|
22
|
+
browserFeaturesData.bitmask = bitmask;
|
|
23
|
+
return browserFeaturesData;
|
|
24
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ERROR, INIT, hashCode } from './utils';
|
|
2
|
+
import { SignalValue } from '../types';
|
|
3
|
+
|
|
4
|
+
async function hasModifiedCanvas(): Promise<SignalValue<boolean>> {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const img = new Image();
|
|
9
|
+
const ctx = document.createElement('canvas').getContext('2d') as CanvasRenderingContext2D;
|
|
10
|
+
img.onload = () => {
|
|
11
|
+
ctx.drawImage(img, 0, 0);
|
|
12
|
+
resolve(ctx.getImageData(0, 0, 1, 1).data.filter(x => x === 0).length != 4);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
img.onerror = () => {
|
|
16
|
+
resolve(ERROR);
|
|
17
|
+
};
|
|
18
|
+
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=';
|
|
19
|
+
} catch (e) {
|
|
20
|
+
resolve(ERROR);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
function getCanvasFingerprint(): SignalValue<string> {
|
|
27
|
+
var canvas = document.createElement('canvas');
|
|
28
|
+
canvas.width = 400;
|
|
29
|
+
canvas.height = 200;
|
|
30
|
+
canvas.style.display = "inline";
|
|
31
|
+
var context = canvas.getContext("2d") as CanvasRenderingContext2D;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
context.rect(0, 0, 10, 10);
|
|
35
|
+
context.rect(2, 2, 6, 6);
|
|
36
|
+
context.textBaseline = "alphabetic";
|
|
37
|
+
context.fillStyle = "#f60";
|
|
38
|
+
context.fillRect(125, 1, 62, 20);
|
|
39
|
+
context.fillStyle = "#069";
|
|
40
|
+
context.font = "11pt no-real-font-123";
|
|
41
|
+
context.fillText("Cwm fjordbank glyphs vext quiz, 😃", 2, 15);
|
|
42
|
+
context.fillStyle = "rgba(102, 204, 0, 0.2)";
|
|
43
|
+
context.font = "18pt Arial";
|
|
44
|
+
context.fillText("Cwm fjordbank glyphs vext quiz, 😃", 4, 45);
|
|
45
|
+
|
|
46
|
+
context.globalCompositeOperation = "multiply";
|
|
47
|
+
context.fillStyle = "rgb(255,0,255)";
|
|
48
|
+
context.beginPath();
|
|
49
|
+
context.arc(50, 50, 50, 0, 2 * Math.PI, !0);
|
|
50
|
+
context.closePath();
|
|
51
|
+
context.fill();
|
|
52
|
+
context.fillStyle = "rgb(0,255,255)";
|
|
53
|
+
context.beginPath();
|
|
54
|
+
context.arc(100, 50, 50, 0, 2 * Math.PI, !0);
|
|
55
|
+
context.closePath();
|
|
56
|
+
context.fill();
|
|
57
|
+
context.fillStyle = "rgb(255,255,0)";
|
|
58
|
+
context.beginPath();
|
|
59
|
+
context.arc(75, 100, 50, 0, 2 * Math.PI, !0);
|
|
60
|
+
context.closePath();
|
|
61
|
+
context.fill();
|
|
62
|
+
context.fillStyle = "rgb(255,0,255)";
|
|
63
|
+
context.arc(75, 75, 75, 0, 2 * Math.PI, !0);
|
|
64
|
+
context.arc(75, 75, 25, 0, 2 * Math.PI, !0);
|
|
65
|
+
context.fill("evenodd");
|
|
66
|
+
return hashCode(canvas.toDataURL());
|
|
67
|
+
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return ERROR;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function canvas() {
|
|
74
|
+
const canvasData = {
|
|
75
|
+
hasModifiedCanvas: INIT as SignalValue<boolean>,
|
|
76
|
+
canvasFingerprint: INIT as SignalValue<string>,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
canvasData.hasModifiedCanvas = await hasModifiedCanvas();
|
|
80
|
+
|
|
81
|
+
canvasData.canvasFingerprint = getCanvasFingerprint();
|
|
82
|
+
|
|
83
|
+
return canvasData;
|
|
84
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ERROR } from './utils';
|
|
2
|
+
|
|
3
|
+
export function cdp() {
|
|
4
|
+
try {
|
|
5
|
+
let wasAccessed = false;
|
|
6
|
+
const originalPrepareStackTrace = (Error as any).prepareStackTrace;
|
|
7
|
+
(Error as any).prepareStackTrace = function () {
|
|
8
|
+
wasAccessed = true;
|
|
9
|
+
return originalPrepareStackTrace;
|
|
10
|
+
};
|
|
11
|
+
const err = new Error('');
|
|
12
|
+
console.log(err);
|
|
13
|
+
|
|
14
|
+
return wasAccessed;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return ERROR;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ERROR, INIT, NA, setObjectValues } from "./utils";
|
|
2
|
+
|
|
3
|
+
export async function highEntropyValues() {
|
|
4
|
+
const navigator = window.navigator as any;
|
|
5
|
+
const highEntropyValues = {
|
|
6
|
+
architecture: INIT,
|
|
7
|
+
bitness: INIT,
|
|
8
|
+
brands: INIT,
|
|
9
|
+
mobile: INIT,
|
|
10
|
+
model: INIT,
|
|
11
|
+
platform: INIT,
|
|
12
|
+
platformVersion: INIT,
|
|
13
|
+
uaFullVersion: INIT,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if ('userAgentData' in navigator) {
|
|
17
|
+
try {
|
|
18
|
+
const ua = await navigator.userAgentData.getHighEntropyValues([
|
|
19
|
+
"architecture",
|
|
20
|
+
"bitness",
|
|
21
|
+
"brands",
|
|
22
|
+
"mobile",
|
|
23
|
+
"model",
|
|
24
|
+
"platform",
|
|
25
|
+
"platformVersion",
|
|
26
|
+
"uaFullVersion"
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
highEntropyValues.architecture = ua.architecture;
|
|
30
|
+
highEntropyValues.bitness = ua.bitness;
|
|
31
|
+
highEntropyValues.brands = ua.brands;
|
|
32
|
+
highEntropyValues.mobile = ua.mobile;
|
|
33
|
+
highEntropyValues.model = ua.model;
|
|
34
|
+
highEntropyValues.platform = ua.platform;
|
|
35
|
+
highEntropyValues.platformVersion = ua.platformVersion;
|
|
36
|
+
highEntropyValues.uaFullVersion = ua.uaFullVersion;
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
} catch (e) {
|
|
40
|
+
setObjectValues(highEntropyValues, ERROR);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
} else {
|
|
44
|
+
setObjectValues(highEntropyValues, NA);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return highEntropyValues;
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ERROR, INIT, NA, setObjectValues } from './utils';
|
|
2
|
+
|
|
3
|
+
export function iframe() {
|
|
4
|
+
const iframeData = {
|
|
5
|
+
webdriver: INIT,
|
|
6
|
+
userAgent: INIT,
|
|
7
|
+
platform: INIT,
|
|
8
|
+
memory: INIT,
|
|
9
|
+
cpuCount: INIT,
|
|
10
|
+
language: INIT,
|
|
11
|
+
};
|
|
12
|
+
const iframe = document.createElement('iframe');
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
iframe.style.display = 'none';
|
|
16
|
+
iframe.src = 'about:blank';
|
|
17
|
+
document.body.appendChild(iframe);
|
|
18
|
+
|
|
19
|
+
const iframeWindowNavigator = (iframe.contentWindow?.navigator as any);
|
|
20
|
+
|
|
21
|
+
iframeData.webdriver = iframeWindowNavigator.webdriver ?? false;
|
|
22
|
+
iframeData.userAgent = iframeWindowNavigator.userAgent ?? NA;
|
|
23
|
+
iframeData.platform = iframeWindowNavigator.platform ?? NA;
|
|
24
|
+
iframeData.memory = iframeWindowNavigator.deviceMemory ?? NA;
|
|
25
|
+
iframeData.cpuCount = iframeWindowNavigator.hardwareConcurrency ?? NA;
|
|
26
|
+
iframeData.language = iframeWindowNavigator.language ?? NA;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
setObjectValues(iframeData, ERROR);
|
|
29
|
+
} finally {
|
|
30
|
+
document.body.removeChild(iframe);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return iframeData;
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { INIT, ERROR, NA } from "./utils";
|
|
2
|
+
|
|
3
|
+
export function internationalization() {
|
|
4
|
+
const internationalizationData = {
|
|
5
|
+
timezone: INIT,
|
|
6
|
+
localeLanguage: INIT,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
if (typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat !== 'undefined') {
|
|
11
|
+
const dtfOptions = Intl.DateTimeFormat().resolvedOptions();
|
|
12
|
+
internationalizationData.timezone = dtfOptions.timeZone;
|
|
13
|
+
internationalizationData.localeLanguage = dtfOptions.locale;
|
|
14
|
+
} else {
|
|
15
|
+
internationalizationData.timezone = NA;
|
|
16
|
+
internationalizationData.localeLanguage = NA;
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
internationalizationData.timezone = ERROR;
|
|
20
|
+
internationalizationData.localeLanguage = ERROR;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return internationalizationData;
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { hashCode } from './utils';
|
|
2
|
+
|
|
3
|
+
export function maths() {
|
|
4
|
+
const results: number[] = [];
|
|
5
|
+
const testValue = 0.123456789;
|
|
6
|
+
|
|
7
|
+
// Math constants
|
|
8
|
+
const constants = ["E", "LN10", "LN2", "LOG10E", "LOG2E", "PI", "SQRT1_2", "SQRT2"];
|
|
9
|
+
constants.forEach(function (name) {
|
|
10
|
+
try {
|
|
11
|
+
results.push((Math as any)[name]);
|
|
12
|
+
} catch (e) {
|
|
13
|
+
results.push(-1);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Math functions (can reveal VM/browser differences)
|
|
18
|
+
const mathFunctions = ["tan", "sin", "exp", "atan", "acosh", "asinh", "atanh", "expm1", "log1p", "sinh"];
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
mathFunctions.forEach(function (name) {
|
|
22
|
+
try {
|
|
23
|
+
results.push((Math as any)[name](testValue));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
results.push(-1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return hashCode(results.map(String).join(","));
|
|
30
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { ERROR, NA, hashCode, setObjectValues } from './utils';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const AUDIO_CODECS = [
|
|
5
|
+
'audio/mp4; codecs="mp4a.40.2"',
|
|
6
|
+
'audio/mpeg;',
|
|
7
|
+
'audio/webm; codecs="vorbis"',
|
|
8
|
+
'audio/ogg; codecs="vorbis"',
|
|
9
|
+
'audio/wav; codecs="1"',
|
|
10
|
+
'audio/ogg; codecs="speex"',
|
|
11
|
+
'audio/ogg; codecs="flac"',
|
|
12
|
+
'audio/3gpp; codecs="samr"',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const VIDEO_CODECS = [
|
|
16
|
+
'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
|
|
17
|
+
'video/mp4; codecs="avc1.42E01E"',
|
|
18
|
+
'video/mp4; codecs="avc1.58A01E"',
|
|
19
|
+
'video/mp4; codecs="avc1.4D401E"',
|
|
20
|
+
'video/mp4; codecs="avc1.64001E"',
|
|
21
|
+
'video/mp4; codecs="mp4v.20.8"',
|
|
22
|
+
'video/mp4; codecs="mp4v.20.240"',
|
|
23
|
+
'video/webm; codecs="vp8"',
|
|
24
|
+
'video/ogg; codecs="theora"',
|
|
25
|
+
'video/ogg; codecs="dirac"',
|
|
26
|
+
'video/3gpp; codecs="mp4v.20.8"',
|
|
27
|
+
'video/x-matroska; codecs="theora"',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
function getCanPlayTypeSupport(codecs: string[], mediaType: 'audio' | 'video'): Record<string, string | null> {
|
|
32
|
+
const result: Record<string, string | null> = {};
|
|
33
|
+
try {
|
|
34
|
+
const element = document.createElement(mediaType);
|
|
35
|
+
for (const codec of codecs) {
|
|
36
|
+
try {
|
|
37
|
+
result[codec] = element.canPlayType(codec) || null;
|
|
38
|
+
} catch {
|
|
39
|
+
result[codec] = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
for (const codec of codecs) {
|
|
44
|
+
result[codec] = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getMediaSourceSupport(codecs: string[]): Record<string, boolean | null> {
|
|
51
|
+
const result: Record<string, boolean | null> = {};
|
|
52
|
+
const MediaSource = window.MediaSource;
|
|
53
|
+
|
|
54
|
+
if (!MediaSource || typeof MediaSource.isTypeSupported !== 'function') {
|
|
55
|
+
for (const codec of codecs) {
|
|
56
|
+
result[codec] = null;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const codec of codecs) {
|
|
62
|
+
try {
|
|
63
|
+
result[codec] = MediaSource.isTypeSupported(codec);
|
|
64
|
+
} catch {
|
|
65
|
+
result[codec] = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getRtcCapabilities(kind: 'audio' | 'video'): string | typeof NA | typeof ERROR {
|
|
72
|
+
try {
|
|
73
|
+
const RTCRtpReceiver = window.RTCRtpReceiver;
|
|
74
|
+
if (RTCRtpReceiver && typeof RTCRtpReceiver.getCapabilities === 'function') {
|
|
75
|
+
const capabilities = RTCRtpReceiver.getCapabilities(kind);
|
|
76
|
+
return hashCode(JSON.stringify(capabilities));
|
|
77
|
+
}
|
|
78
|
+
return NA;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return ERROR;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function mediaCodecs() {
|
|
85
|
+
const mediaCodecsData = {
|
|
86
|
+
audioCanPlayTypeHash: NA as string | typeof NA | typeof ERROR,
|
|
87
|
+
videoCanPlayTypeHash: NA as string | typeof NA | typeof ERROR,
|
|
88
|
+
audioMediaSourceHash: NA as string | typeof NA | typeof ERROR,
|
|
89
|
+
videoMediaSourceHash: NA as string | typeof NA | typeof ERROR,
|
|
90
|
+
rtcAudioCapabilitiesHash: NA as string | typeof NA | typeof ERROR,
|
|
91
|
+
rtcVideoCapabilitiesHash: NA as string | typeof NA | typeof ERROR,
|
|
92
|
+
hasMediaSource: false,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Check MediaSource availability
|
|
97
|
+
mediaCodecsData.hasMediaSource = !!window.MediaSource;
|
|
98
|
+
|
|
99
|
+
// canPlayType support - hash the results
|
|
100
|
+
const audioCanPlayType = getCanPlayTypeSupport(AUDIO_CODECS, 'audio');
|
|
101
|
+
const videoCanPlayType = getCanPlayTypeSupport(VIDEO_CODECS, 'video');
|
|
102
|
+
mediaCodecsData.audioCanPlayTypeHash = hashCode(JSON.stringify(audioCanPlayType));
|
|
103
|
+
mediaCodecsData.videoCanPlayTypeHash = hashCode(JSON.stringify(videoCanPlayType));
|
|
104
|
+
|
|
105
|
+
// MediaSource.isTypeSupported - hash the results
|
|
106
|
+
const audioMediaSource = getMediaSourceSupport(AUDIO_CODECS);
|
|
107
|
+
const videoMediaSource = getMediaSourceSupport(VIDEO_CODECS);
|
|
108
|
+
mediaCodecsData.audioMediaSourceHash = hashCode(JSON.stringify(audioMediaSource));
|
|
109
|
+
mediaCodecsData.videoMediaSourceHash = hashCode(JSON.stringify(videoMediaSource));
|
|
110
|
+
|
|
111
|
+
// RTCRtpReceiver.getCapabilities - already returns hash
|
|
112
|
+
mediaCodecsData.rtcAudioCapabilitiesHash = getRtcCapabilities('audio');
|
|
113
|
+
mediaCodecsData.rtcVideoCapabilitiesHash = getRtcCapabilities('video');
|
|
114
|
+
|
|
115
|
+
} catch (e) {
|
|
116
|
+
setObjectValues(mediaCodecsData, ERROR);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return mediaCodecsData;
|
|
120
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ERROR, INIT, setObjectValues } from './utils';
|
|
2
|
+
|
|
3
|
+
export function mediaQueries() {
|
|
4
|
+
const mediaQueriesData = {
|
|
5
|
+
prefersColorScheme: INIT as string | null | typeof INIT | typeof ERROR,
|
|
6
|
+
prefersReducedMotion: INIT as boolean | typeof INIT | typeof ERROR,
|
|
7
|
+
prefersReducedTransparency: INIT as boolean | typeof INIT | typeof ERROR,
|
|
8
|
+
colorGamut: INIT as string | null | typeof INIT | typeof ERROR,
|
|
9
|
+
pointer: INIT as string | null | typeof INIT | typeof ERROR,
|
|
10
|
+
anyPointer: INIT as string | null | typeof INIT | typeof ERROR,
|
|
11
|
+
hover: INIT as boolean | typeof INIT | typeof ERROR,
|
|
12
|
+
anyHover: INIT as boolean | typeof INIT | typeof ERROR,
|
|
13
|
+
colorDepth: INIT as number | typeof INIT | typeof ERROR,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Prefers color scheme
|
|
18
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
19
|
+
mediaQueriesData.prefersColorScheme = 'dark';
|
|
20
|
+
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
|
21
|
+
mediaQueriesData.prefersColorScheme = 'light';
|
|
22
|
+
} else {
|
|
23
|
+
mediaQueriesData.prefersColorScheme = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Prefers reduced motion
|
|
27
|
+
mediaQueriesData.prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
28
|
+
|
|
29
|
+
// Prefers reduced transparency
|
|
30
|
+
mediaQueriesData.prefersReducedTransparency = window.matchMedia('(prefers-reduced-transparency: reduce)').matches;
|
|
31
|
+
|
|
32
|
+
// Color gamut
|
|
33
|
+
if (window.matchMedia('(color-gamut: rec2020)').matches) {
|
|
34
|
+
mediaQueriesData.colorGamut = 'rec2020';
|
|
35
|
+
} else if (window.matchMedia('(color-gamut: p3)').matches) {
|
|
36
|
+
mediaQueriesData.colorGamut = 'p3';
|
|
37
|
+
} else if (window.matchMedia('(color-gamut: srgb)').matches) {
|
|
38
|
+
mediaQueriesData.colorGamut = 'srgb';
|
|
39
|
+
} else {
|
|
40
|
+
mediaQueriesData.colorGamut = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Pointer
|
|
44
|
+
if (window.matchMedia('(pointer: fine)').matches) {
|
|
45
|
+
mediaQueriesData.pointer = 'fine';
|
|
46
|
+
} else if (window.matchMedia('(pointer: coarse)').matches) {
|
|
47
|
+
mediaQueriesData.pointer = 'coarse';
|
|
48
|
+
} else if (window.matchMedia('(pointer: none)').matches) {
|
|
49
|
+
mediaQueriesData.pointer = 'none';
|
|
50
|
+
} else {
|
|
51
|
+
mediaQueriesData.pointer = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Any pointer
|
|
55
|
+
if (window.matchMedia('(any-pointer: fine)').matches) {
|
|
56
|
+
mediaQueriesData.anyPointer = 'fine';
|
|
57
|
+
} else if (window.matchMedia('(any-pointer: coarse)').matches) {
|
|
58
|
+
mediaQueriesData.anyPointer = 'coarse';
|
|
59
|
+
} else if (window.matchMedia('(any-pointer: none)').matches) {
|
|
60
|
+
mediaQueriesData.anyPointer = 'none';
|
|
61
|
+
} else {
|
|
62
|
+
mediaQueriesData.anyPointer = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Hover
|
|
66
|
+
mediaQueriesData.hover = window.matchMedia('(hover: hover)').matches;
|
|
67
|
+
|
|
68
|
+
// Any hover
|
|
69
|
+
mediaQueriesData.anyHover = window.matchMedia('(any-hover: hover)').matches;
|
|
70
|
+
|
|
71
|
+
// Color depth - find the maximum supported color depth
|
|
72
|
+
let maxColorDepth = 0;
|
|
73
|
+
for (let c = 0; c <= 16; c++) {
|
|
74
|
+
if (window.matchMedia(`(color: ${c})`).matches) {
|
|
75
|
+
maxColorDepth = c;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
mediaQueriesData.colorDepth = maxColorDepth;
|
|
79
|
+
|
|
80
|
+
} catch (e) {
|
|
81
|
+
setObjectValues(mediaQueriesData, ERROR);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return mediaQueriesData;
|
|
85
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NA, setObjectValues } from "./utils";
|
|
2
|
+
|
|
3
|
+
export async function multimediaDevices() {
|
|
4
|
+
return new Promise(async function (resolve) {
|
|
5
|
+
var deviceToCount = {
|
|
6
|
+
"audiooutput": 0,
|
|
7
|
+
"audioinput": 0,
|
|
8
|
+
"videoinput": 0
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
|
12
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
13
|
+
if (typeof devices !== "undefined") {
|
|
14
|
+
for (var i = 0; i < devices.length; i++) {
|
|
15
|
+
var name = devices[i].kind as keyof typeof deviceToCount;
|
|
16
|
+
deviceToCount[name] = deviceToCount[name] + 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return resolve({
|
|
20
|
+
speakers: deviceToCount.audiooutput,
|
|
21
|
+
microphones: deviceToCount.audioinput,
|
|
22
|
+
webcams: deviceToCount.videoinput
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
setObjectValues(deviceToCount, NA);
|
|
26
|
+
return resolve(deviceToCount);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
} else {
|
|
30
|
+
setObjectValues(deviceToCount, NA);
|
|
31
|
+
return resolve(deviceToCount);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function navigatorPropertyDescriptors() {
|
|
2
|
+
const properties = ['deviceMemory', 'hardwareConcurrency', 'language', 'languages', 'platform'];
|
|
3
|
+
|
|
4
|
+
const results = [];
|
|
5
|
+
|
|
6
|
+
for (const property of properties) {
|
|
7
|
+
const res = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(navigator), property);
|
|
8
|
+
|
|
9
|
+
if (res && res.value) {
|
|
10
|
+
results.push('1');
|
|
11
|
+
} else {
|
|
12
|
+
results.push('0');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return results.join('');
|
|
17
|
+
}
|