fpscanner 1.0.2 → 1.0.4-beta.0
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/dist/detections/hasMismatchPlatformWorker.d.ts.map +1 -1
- package/dist/detections/hasMismatchWebGLInWorker.d.ts.map +1 -1
- package/dist/detections/hasMissingChromeObject.d.ts.map +1 -1
- package/dist/fpScanner.cjs.js +32 -16
- package/dist/fpScanner.es.js +233 -200
- package/dist/signals/worker.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/detections/hasMismatchPlatformWorker.ts +27 -1
- package/src/detections/hasMismatchWebGLInWorker.ts +15 -3
- package/src/detections/hasMissingChromeObject.ts +21 -1
- package/src/signals/worker.ts +40 -23
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/signals/worker.ts"],"names":[],"mappings":"AAEA,wBAAsB,MAAM,
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/signals/worker.ts"],"names":[],"mappings":"AAEA,wBAAsB,MAAM,qBAmH3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fpscanner",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4-beta.0",
|
|
4
4
|
"description": "A lightweight browser fingerprinting and bot detection library with encryption, obfuscation, and cross-context validation",
|
|
5
5
|
"main": "dist/fpScanner.cjs.js",
|
|
6
6
|
"module": "dist/fpScanner.es.js",
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import { Fingerprint } from "../types";
|
|
2
2
|
import { ERROR, NA, SKIPPED } from "../signals/utils";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* iPad Safari in desktop-style mode often reports navigator.platform as "MacIntel"
|
|
6
|
+
* in the top-level window (and same-origin iframe) while dedicated workers can still
|
|
7
|
+
* report "iPad". That split is expected on real hardware, not a worker spoof signal.
|
|
8
|
+
*/
|
|
9
|
+
function isBenignIPadMacPlatformSplit(a: string, b: string): boolean {
|
|
10
|
+
const aIsIPad = a.includes("iPad");
|
|
11
|
+
const bIsIPad = b.includes("iPad");
|
|
12
|
+
if (aIsIPad === bIsIPad) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const macDesktopCompat = (p: string) => p === "MacIntel" || p === "MacPPC";
|
|
16
|
+
return macDesktopCompat(a) || macDesktopCompat(b);
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
export function hasMismatchPlatformWorker(fingerprint: Fingerprint) {
|
|
5
20
|
if (fingerprint.signals.contexts.webWorker.platform === NA || fingerprint.signals.contexts.webWorker.platform === ERROR || fingerprint.signals.contexts.webWorker.platform === SKIPPED) {
|
|
6
21
|
return false;
|
|
7
22
|
}
|
|
8
23
|
|
|
9
|
-
|
|
24
|
+
const devicePlatform = fingerprint.signals.device.platform;
|
|
25
|
+
const workerPlatform = fingerprint.signals.contexts.webWorker.platform;
|
|
26
|
+
|
|
27
|
+
if (devicePlatform === workerPlatform) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isBenignIPadMacPlatformSplit(devicePlatform, workerPlatform)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return true;
|
|
10
36
|
}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { Fingerprint } from "../types";
|
|
2
|
-
import { ERROR, NA, SKIPPED } from "../signals/utils";
|
|
2
|
+
import { ERROR, INIT, NA, SKIPPED } from "../signals/utils";
|
|
3
|
+
|
|
4
|
+
function webGLStringUnavailable(value: unknown): boolean {
|
|
5
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
return value === NA || value === ERROR || value === SKIPPED || value === INIT;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
export function hasMismatchWebGLInWorker(fingerprint: Fingerprint) {
|
|
5
12
|
const worker = fingerprint.signals.contexts.webWorker;
|
|
6
13
|
const webGL = fingerprint.signals.graphics.webGL;
|
|
7
|
-
|
|
8
|
-
if (
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
webGLStringUnavailable(webGL.vendor) ||
|
|
17
|
+
webGLStringUnavailable(webGL.renderer) ||
|
|
18
|
+
webGLStringUnavailable(worker.vendor) ||
|
|
19
|
+
webGLStringUnavailable(worker.renderer)
|
|
20
|
+
) {
|
|
9
21
|
return false;
|
|
10
22
|
}
|
|
11
23
|
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { Fingerprint } from "../types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* In-app browsers and system WebViews on Android/iOS often ship a Chrome-like UA but
|
|
5
|
+
* do not expose window.chrome. That is common on real devices, so this rule is only
|
|
6
|
+
* meant for desktop-style environments where missing chrome is suspicious.
|
|
7
|
+
*/
|
|
8
|
+
function isClaimedMobileUserAgent(ua: string): boolean {
|
|
9
|
+
return (
|
|
10
|
+
ua.includes("Android") ||
|
|
11
|
+
ua.includes("iPhone") ||
|
|
12
|
+
ua.includes("iPod") ||
|
|
13
|
+
ua.includes("iPad")
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
export function hasMissingChromeObject(fingerprint: Fingerprint) {
|
|
4
18
|
const userAgent = fingerprint.signals.browser.userAgent;
|
|
5
|
-
|
|
19
|
+
if (typeof userAgent !== "string" || !userAgent.includes("Chrome")) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (isClaimedMobileUserAgent(userAgent)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return fingerprint.signals.browser.features.chrome === false;
|
|
6
26
|
}
|
package/src/signals/worker.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ERROR, INIT, setObjectValues } from './utils';
|
|
1
|
+
import { ERROR, INIT, NA, setObjectValues } from './utils';
|
|
2
2
|
|
|
3
3
|
export async function worker() {
|
|
4
4
|
return new Promise((resolve) => {
|
|
@@ -23,29 +23,45 @@ export async function worker() {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
const workerCode = `
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const workerCode = `var fingerprintWorker = {
|
|
27
|
+
userAgent: 'NA',
|
|
28
|
+
language: 'NA',
|
|
29
|
+
cpuCount: 'NA',
|
|
30
|
+
platform: 'NA',
|
|
31
|
+
memory: 'NA',
|
|
32
|
+
vendor: 'NA',
|
|
33
|
+
renderer: 'NA'
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
29
36
|
fingerprintWorker.userAgent = navigator.userAgent;
|
|
30
37
|
fingerprintWorker.language = navigator.language;
|
|
31
38
|
fingerprintWorker.cpuCount = navigator.hardwareConcurrency;
|
|
32
39
|
fingerprintWorker.platform = navigator.platform;
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
if (typeof navigator.deviceMemory !== 'undefined') {
|
|
41
|
+
fingerprintWorker.memory = navigator.deviceMemory;
|
|
42
|
+
}
|
|
35
43
|
|
|
36
|
-
var canvas = new OffscreenCanvas(1, 1);
|
|
37
|
-
fingerprintWorker.vendor = 'INIT';
|
|
38
|
-
fingerprintWorker.renderer = 'INIT';
|
|
39
|
-
var gl = canvas.getContext('webgl');
|
|
40
|
-
const isFirefox = navigator.userAgent.includes('Firefox');
|
|
41
44
|
try {
|
|
42
|
-
if (
|
|
43
|
-
var glExt = gl.getExtension('WEBGL_debug_renderer_info');
|
|
44
|
-
fingerprintWorker.vendor = gl.getParameter(glExt.UNMASKED_VENDOR_WEBGL);
|
|
45
|
-
fingerprintWorker.renderer = gl.getParameter(glExt.UNMASKED_RENDERER_WEBGL);
|
|
46
|
-
} else {
|
|
45
|
+
if (typeof OffscreenCanvas === 'undefined') {
|
|
47
46
|
fingerprintWorker.vendor = 'NA';
|
|
48
47
|
fingerprintWorker.renderer = 'NA';
|
|
48
|
+
} else {
|
|
49
|
+
var canvas = new OffscreenCanvas(1, 1);
|
|
50
|
+
var gl = canvas.getContext('webgl');
|
|
51
|
+
var isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
|
52
|
+
if (gl && !isFirefox) {
|
|
53
|
+
var glExt = gl.getExtension('WEBGL_debug_renderer_info');
|
|
54
|
+
if (glExt) {
|
|
55
|
+
fingerprintWorker.vendor = gl.getParameter(glExt.UNMASKED_VENDOR_WEBGL);
|
|
56
|
+
fingerprintWorker.renderer = gl.getParameter(glExt.UNMASKED_RENDERER_WEBGL);
|
|
57
|
+
} else {
|
|
58
|
+
fingerprintWorker.vendor = 'NA';
|
|
59
|
+
fingerprintWorker.renderer = 'NA';
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
fingerprintWorker.vendor = 'NA';
|
|
63
|
+
fingerprintWorker.renderer = 'NA';
|
|
64
|
+
}
|
|
49
65
|
}
|
|
50
66
|
} catch (_) {
|
|
51
67
|
fingerprintWorker.vendor = 'ERROR';
|
|
@@ -70,13 +86,14 @@ export async function worker() {
|
|
|
70
86
|
|
|
71
87
|
worker.onmessage = function (e) {
|
|
72
88
|
try {
|
|
73
|
-
|
|
74
|
-
workerData.
|
|
75
|
-
workerData.
|
|
76
|
-
workerData.
|
|
77
|
-
workerData.
|
|
78
|
-
workerData.
|
|
79
|
-
workerData.
|
|
89
|
+
const pick = (v: any) => (typeof v === 'undefined' ? NA : v);
|
|
90
|
+
workerData.vendor = pick(e.data.vendor);
|
|
91
|
+
workerData.renderer = pick(e.data.renderer);
|
|
92
|
+
workerData.userAgent = pick(e.data.userAgent);
|
|
93
|
+
workerData.language = pick(e.data.language);
|
|
94
|
+
workerData.platform = pick(e.data.platform);
|
|
95
|
+
workerData.memory = pick(e.data.memory);
|
|
96
|
+
workerData.cpuCount = pick(e.data.cpuCount);
|
|
80
97
|
} catch (_) {
|
|
81
98
|
setObjectValues(workerData, ERROR);
|
|
82
99
|
} finally {
|