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...
-
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis, suscipit assumenda dicta, iste, eius possimus sunt asperiores debitis quam voluptatum deleniti obcaecati facere nisi deserunt reprehenderit nobis sapiente explicabo non.
+
+Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis, suscipit assumenda dicta, iste, eius possimus sunt asperiores debitis quam voluptatum deleniti obcaecati facere nisi deserunt reprehenderit nobis sapiente explicabo non.
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 -}