diff --git a/package.json b/package.json
index 20ae87b6..65307793 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@fingerprintjs/botd",
- "version": "1.2.0",
+ "version": "1.3.0",
"description": "botd is a browser library for JavaScript bot detection",
"keywords": [
"bot",
diff --git a/playground/index.html b/playground/index.html
index 83177502..149060c2 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -31,7 +31,9 @@
Detecting...
diff --git a/playground/style.css b/playground/style.css
index 94f3dec0..384232a4 100644
--- a/playground/style.css
+++ b/playground/style.css
@@ -3,10 +3,9 @@
--accent-dark-color: rgb(140, 39, 3);
--error-color: #b3261e;
--font-family: 'Fira Mono', monospace;
- --font-family-consolas: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ --font-family-consolas: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
}
-
body {
width: 100%;
height: 100%;
@@ -27,7 +26,6 @@ body {
box-sizing: border-box;
}
-
.container {
width: calc(100% - 128px);
max-width: 1536px;
@@ -90,7 +88,7 @@ body {
margin-top: 32px;
}
- #navbar .logo {
+ #navbar .logo {
margin-top: 0;
width: 256px;
}
@@ -98,7 +96,7 @@ body {
#navbar #navbar-chevron {
transform: rotate(90deg);
}
-
+
#navbar #playground-actions {
margin-left: 0;
margin: 32px 0px;
@@ -139,14 +137,15 @@ h2 {
transition: 0.15s;
}
-.github-card:active, .github-card:focus {
+.github-card:active,
+.github-card:focus {
background-color: rgba(0, 0, 0, 0.12);
transition: 0.15s;
}
.github-card::before {
display: block;
- content: "";
+ content: '';
width: 32px;
height: 32px;
background-image: url(../resources/github_icon.svg);
@@ -177,7 +176,6 @@ h2 {
font-weight: 600;
}
-
.orange-button {
background-color: var(--accent-color);
color: #fff;
@@ -190,11 +188,15 @@ h2 {
background-color: transparent;
}
-.orange-button:hover, .orange-button:active, .orange-button:focus {
+.orange-button:hover,
+.orange-button:active,
+.orange-button:focus {
background-color: var(--accent-dark-color);
}
-.orange-button-outlined:hover, .orange-button-outlined:active, .orange-button-outlined:focus {
+.orange-button-outlined:hover,
+.orange-button-outlined:active,
+.orange-button-outlined:focus {
background-color: rgba(240, 68, 5, 0.12);
}
@@ -208,7 +210,6 @@ h2 {
padding: var(--content-card-padding);
}
-
#result {
overflow: hidden;
margin: 96px auto;
@@ -219,7 +220,6 @@ h2 {
--bot-icon-url: url(../resources/robot-off-outline.svg);
}
-
.result-detected {
--bot-icon-color: #f44336 !important;
--bot-icon-url: url(../resources/robot-outline.svg) !important;
@@ -230,7 +230,6 @@ h2 {
--bot-icon-url: url(../resources/alert-circle-outline.svg) !important;
}
-
.result-bot-icon-container {
width: 128px;
height: 128px;
@@ -241,7 +240,7 @@ h2 {
}
.result-bot-icon-container::before {
- content: "";
+ content: '';
display: block;
background-color: var(--bot-icon-color);
opacity: 0.08;
@@ -276,7 +275,6 @@ h2 {
transition: 0.2s color;
}
-
.logs-content {
background-color: #282c34;
color: #fff;
diff --git a/src/detectors/document_attributes.ts b/src/detectors/document_attributes.ts
deleted file mode 100644
index 67f43a20..00000000
--- a/src/detectors/document_attributes.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { BotKind, ComponentDict, DetectorResponse, State } from '../types'
-import { includes } from '../utils/misc'
-
-export function detectDocumentAttributes({ documentAttributes }: ComponentDict): DetectorResponse {
- if (documentAttributes.state !== State.Success) return false
- if (includes(documentAttributes.value, 'selenium', 'webdriver', 'driver')) {
- return BotKind.Selenium
- }
-}
diff --git a/src/detectors/document_element_keys.ts b/src/detectors/document_element_keys.ts
new file mode 100644
index 00000000..a6d091fa
--- /dev/null
+++ b/src/detectors/document_element_keys.ts
@@ -0,0 +1,9 @@
+import { BotKind, ComponentDict, DetectorResponse, State } from '../types'
+import { includes } from '../utils/misc'
+
+export function detectDocumentAttributes({ documentElementKeys }: ComponentDict): DetectorResponse {
+ if (documentElementKeys.state !== State.Success) return false
+ if (includes(documentElementKeys.value, 'selenium', 'webdriver', 'driver')) {
+ return BotKind.Selenium
+ }
+}
diff --git a/src/detectors/index.ts b/src/detectors/index.ts
index cfab6ae0..c37b03bc 100644
--- a/src/detectors/index.ts
+++ b/src/detectors/index.ts
@@ -1,5 +1,5 @@
import { detectAppVersion } from './app_version'
-import { detectDocumentAttributes } from './document_attributes'
+import { detectDocumentAttributes } from './document_element_keys'
import { detectDocumentProperties } from './document_properties'
import { detectErrorTrace } from './error_trace'
import { detectEvalLengthInconsistency } from './eval_length'
diff --git a/src/sources/device_memory.ts b/src/sources/device_memory.ts
deleted file mode 100644
index f9638981..00000000
--- a/src/sources/device_memory.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { BotdError, State } from '../types'
-import { replaceNaN, toFloat } from '../utils/data'
-
-export default function getDeviceMemory(): number {
- // `navigator.deviceMemory` is a string containing a number in some unidentified cases
- const deviceMemory = replaceNaN(toFloat(navigator.deviceMemory), undefined)
- if (deviceMemory == undefined) {
- throw new BotdError(State.Undefined, 'navigator.osdeviceMemorycpu is undefined')
- }
- return deviceMemory
-}
diff --git a/src/sources/document_element_attributes.ts b/src/sources/document_element_attributes.ts
deleted file mode 100644
index ad2aeddf..00000000
--- a/src/sources/document_element_attributes.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { BotdError, State } from '../types'
-
-export default function getDocumentAttributes(): string[] {
- if (document.documentElement === undefined) {
- throw new BotdError(State.Undefined, 'document.documentElement is undefined')
- }
- return Array.from(document.documentElement.attributes).map((r) => r.name)
-}
diff --git a/src/sources/document_element_keys.ts b/src/sources/document_element_keys.ts
new file mode 100644
index 00000000..33fe491c
--- /dev/null
+++ b/src/sources/document_element_keys.ts
@@ -0,0 +1,12 @@
+import { BotdError, State } from '../types'
+
+export default function getDocumentElementKeys(): string[] {
+ if (document.documentElement === undefined) {
+ throw new BotdError(State.Undefined, 'document.documentElement is undefined')
+ }
+ const { documentElement } = document
+ if (typeof documentElement.getAttributeNames !== 'function') {
+ throw new BotdError(State.NotFunction, 'document.documentElement.getAttributeNames is not a function')
+ }
+ return documentElement.getAttributeNames()
+}
diff --git a/src/sources/hardware_concurrency.ts b/src/sources/hardware_concurrency.ts
deleted file mode 100644
index fb2c9abe..00000000
--- a/src/sources/hardware_concurrency.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { BotdError, State } from '../types'
-import { replaceNaN, toInt } from '../utils/data'
-
-export default function getHardwareConcurrency(): number | undefined {
- const hardwareConcurrency = replaceNaN(toInt(navigator.hardwareConcurrency), undefined)
- if (hardwareConcurrency == undefined) {
- throw new BotdError(State.Undefined, 'navigator.hardwareConcurrency is undefined')
- }
- return hardwareConcurrency
-}
diff --git a/src/sources/index.ts b/src/sources/index.ts
index 35547ae5..bca0df5b 100644
--- a/src/sources/index.ts
+++ b/src/sources/index.ts
@@ -1,25 +1,18 @@
import getAppVersion from './app_version'
-import getDeviceMemory from './device_memory'
-import getDocumentAttributes from './document_element_attributes'
+import getDocumentElementKeys from './document_element_keys'
import getDocumentProperties from './document_properties'
import getErrorTrace from './error_trace'
import getEvalLength from './eval_length'
import getFunctionBind from './function_bind'
-import getHardwareConcurrency from './hardware_concurrency'
import getLanguages from './languages'
import areMimeTypesConsistent from './mime_types_consistence'
import getNotificationPermissions from './notification_permissions'
-import getOsCpu from './os_cpu'
-import getPlatform from './platform'
import getPluginsArray from './plugins_array'
import getPluginsLength from './plugins_length'
import getProcess, { ProcessPayload } from './process'
import getProductSub from './product_sub'
import getRTT from './rtt'
-import getScreenResolution from './screen_resolution'
-import getTouchSupport from './touch_support'
import getUserAgent from './user_agent'
-import getVendor from './vendor'
import getWebDriver from './webdriver'
import getWebGL from './webgl'
import getWindowExternal from './window_external'
@@ -42,18 +35,11 @@ export const sources = {
webDriver: getWebDriver,
languages: getLanguages,
notificationPermissions: getNotificationPermissions,
- documentAttributes: getDocumentAttributes,
+ documentElementKeys: getDocumentElementKeys,
functionBind: getFunctionBind,
process: getProcess,
documentProps: getDocumentProperties,
windowProps: getWindowProperties,
- osCpu: getOsCpu,
- deviceMemory: getDeviceMemory,
- screenResolution: getScreenResolution,
- hardwareConcurrency: getHardwareConcurrency,
- platform: getPlatform,
- touchSupport: getTouchSupport,
- vendor: getVendor,
}
export { WindowSizePayload, ProcessPayload }
diff --git a/src/sources/languages.ts b/src/sources/languages.ts
index ab2b8836..7dca2be4 100644
--- a/src/sources/languages.ts
+++ b/src/sources/languages.ts
@@ -13,7 +13,7 @@ export default function getLanguages(): string[][] {
if (Array.isArray(n.languages)) {
const browserEngine = getBrowserEngineKind()
// Starting from Chromium 86, there is only a single value in `navigator.language` in Incognito mode:
- // the value of `navigator.language`. Therefore the value is ignored in this browser.
+ // the value of `navigator.language`. Therefore, the value is ignored in this browser.
if (!(browserEngine === BrowserEngineKind.Chromium && isChromium86OrNewer())) {
result.push(n.languages)
}
diff --git a/src/sources/os_cpu.ts b/src/sources/os_cpu.ts
deleted file mode 100644
index 41fc251d..00000000
--- a/src/sources/os_cpu.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { BotdError, State } from '../types'
-
-export default function getOsCpu(): string {
- const oscpu = navigator.oscpu
- if (oscpu == undefined) {
- throw new BotdError(State.Undefined, 'navigator.oscpu is undefined')
- }
- return oscpu
-}
diff --git a/src/sources/platform.ts b/src/sources/platform.ts
deleted file mode 100644
index 59133a73..00000000
--- a/src/sources/platform.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { BotdError, BrowserEngineKind, State } from '../types'
-import { getBrowserEngineKind, isDesktopSafari, isIPad } from '../utils/browser'
-
-export default function getPlatform(): string {
- // Android Chrome 86 and 87 and Android Firefox 80 and 84 don't mock the platform value when desktop mode is requested
- const platform = navigator.platform
- if (platform == undefined) {
- throw new BotdError(State.Undefined, 'navigator.platform is undefined')
- }
-
- // iOS mocks the platform value when desktop version is requested: https://github.com/fingerprintjs/fingerprintjs/issues/514
- // iPad uses desktop mode by default since iOS 13
- // The value is 'MacIntel' on M1 Macs
- // The value is 'iPhone' on iPod Touch
- if (platform === 'MacIntel') {
- const browserEngine = getBrowserEngineKind()
- if (browserEngine === BrowserEngineKind.Webkit && !isDesktopSafari()) {
- return isIPad() ? 'iPad' : 'iPhone'
- }
- }
-
- return platform
-}
diff --git a/src/sources/screen_resolution.ts b/src/sources/screen_resolution.ts
deleted file mode 100644
index 3abd2887..00000000
--- a/src/sources/screen_resolution.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { replaceNaN, toInt } from '../utils/data'
-
-export interface ScreenResolutionPayload {
- width?: number
- height?: number
-}
-
-export default function getScreenResolution(): ScreenResolutionPayload {
- const s = screen
-
- // Some browsers return screen resolution as strings, e.g. "1200", instead of a number, e.g. 1200.
- // I suspect it's done by certain plugins that randomize browser properties to prevent fingerprinting.
- // Some browsers even return screen resolution as not numbers.
- const parseDimension = (value: unknown) => replaceNaN(toInt(value), undefined)
- const [height, width] = [parseDimension(s.width), parseDimension(s.height)].sort()
-
- return {
- width,
- height,
- }
-}
diff --git a/src/sources/touch_support.ts b/src/sources/touch_support.ts
deleted file mode 100644
index 3a245155..00000000
--- a/src/sources/touch_support.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { toInt } from '../utils/data'
-
-export interface TouchSupportPayload {
- maxTouchPoints: number
- /** The success or failure of creating a TouchEvent */
- touchEvent: boolean
- /** The availability of the "ontouchstart" property */
- touchStart: boolean
-}
-
-/**
- * This is a crude and primitive touch screen detection. It's not possible to currently reliably detect the availability
- * of a touch screen with a JS, without actually subscribing to a touch event.
- *
- * @see http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
- * @see https://github.com/Modernizr/Modernizr/issues/548
- */
-export default function getTouchSupport(): TouchSupportPayload {
- const n = navigator
-
- let maxTouchPoints = 0
- let touchEvent: boolean
-
- if (n.maxTouchPoints !== undefined) {
- maxTouchPoints = toInt(n.maxTouchPoints)
- } else if (n.msMaxTouchPoints !== undefined) {
- maxTouchPoints = n.msMaxTouchPoints
- }
-
- try {
- document.createEvent('TouchEvent')
- touchEvent = true
- } catch {
- touchEvent = false
- }
-
- const touchStart = 'ontouchstart' in window
-
- return {
- maxTouchPoints,
- touchEvent,
- touchStart,
- }
-}
diff --git a/src/sources/vendor.ts b/src/sources/vendor.ts
deleted file mode 100644
index d7506f2f..00000000
--- a/src/sources/vendor.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { BotdError, State } from '../types'
-
-export default function getVendor(): string {
- const vendor = navigator.vendor
- if (vendor == undefined) {
- throw new BotdError(State.Undefined, 'navigator.vendor is undefined')
- }
- return vendor
-}