Skip to content

Commit

Permalink
feat: botDetectionService (experimental)
Browse files Browse the repository at this point in the history
  • Loading branch information
kirillgroshkov committed Oct 8, 2024
1 parent d487a58 commit 7afb2aa
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/bot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { botDetectionService } from './bot'

test('botDetectionService', () => {
expect(botDetectionService.isBot()).toBe(false)
expect(botDetectionService.isCDP()).toBe(false)
expect(botDetectionService.isBotOrCDP()).toBe(false)
})
85 changes: 85 additions & 0 deletions src/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Relevant material:
// https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024

import { isServerSide } from '@naturalcycles/js-lib'

/**
* Service to detect bots and CDP (Chrome DevTools Protocol).
*
* @experimental
*/
class BotDetectionService {
isBotOrCDP(): boolean {
return this.isBot() || this.isCDP()
}

isBot(): boolean {
// SSR - not a bot
if (isServerSide() || !navigator) return false

const { userAgent } = navigator
if (!userAgent) return true

if (/Headless/i.test(userAgent)) return true
if (/Electron/i.test(userAgent)) return true
if (/PhantomJS/i.test(userAgent)) return true
if (/slimerjs/i.test(userAgent)) return true

if (navigator.webdriver) {
return true
}

if (navigator.plugins?.length === 0) {
return true // Headless Chrome
}

if ((navigator.languages as any) === '') {
return true // Headless Chrome
}

// isChrome is true if the browser is Chrome, Chromium or Opera
// if(isChrome && !window.chrome) {
// return true // Headless Chrome
// }

return false
}

/**
* CDP stands for Chrome DevTools Protocol.
* This function tests if the current environment is a CDP environment.
* If it's true - it's one of:
*
* 1. Bot, automated with CDP, e.g Puppeteer, Playwright or such.
* 2. Developer with Chrome DevTools open.
*
* 2 is certainly not a bot, but unfortunately we can't distinguish between the two.
* That's why this function is not part of `isBot()`, because it can give "false positive" with DevTools.
*
* Based on: https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
*/
isCDP(): boolean {
if (isServerSide()) return false
let cdpCheck1 = false
try {
/* eslint-disable */
// biome-ignore lint/suspicious/useErrorMessage: ok
const e = new window.Error()
window.Object.defineProperty(e, 'stack', {
configurable: false,
enumerable: false,
// biome-ignore lint/complexity/useArrowFunction: ok
get: function () {
cdpCheck1 = true
return ''
},
})
// This is part of the detection and shouldn't be deleted
window.console.debug(e)
/* eslint-enable */
} catch {}
return cdpCheck1
}
}

export const botDetectionService = new BotDetectionService()
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './admin/adminService'
export * from './analytics/analytics'
export * from './bot'
export * from './i18n/fetchTranslationLoader'
export * from './i18n/translation.service'
export * from './image/imageFitter'
Expand Down

0 comments on commit 7afb2aa

Please sign in to comment.