@axeptio/behavior-detection 1.0.3 → 1.1.1

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.
@@ -140,7 +140,6 @@ class BehaviorDetectionAPI {
140
140
  */
141
141
  start() {
142
142
  if (!this.detector) {
143
- console.warn('BehaviorDetector: Call init() before start()');
144
143
  return;
145
144
  }
146
145
  this.detector.start();
@@ -162,7 +161,6 @@ class BehaviorDetectionAPI {
162
161
  */
163
162
  async score() {
164
163
  if (!this.detector) {
165
- console.warn('BehaviorDetector: Call init() before score()');
166
164
  return null;
167
165
  }
168
166
  return this.detector.score({ breakdown: true });
@@ -33,6 +33,6 @@
33
33
  */
34
34
  export { BehaviorDetector } from './behavior-detector.js';
35
35
  export type { DetectionStrategy, StrategyConfig } from './strategy.js';
36
- export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, } from './strategies/index.js';
36
+ export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, Timing, Visibility, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, TimingStrategy, VisibilityStrategy, } from './strategies/index.js';
37
37
  export type { BehaviorSettings, ScoreOptions, ScoreResult, ScoreBreakdown, TrackedEvent, EventType, ScoringFunction, } from './types.js';
38
38
  export { DEFAULT_SETTINGS } from './types.js';
package/dist/esm/index.js CHANGED
@@ -32,5 +32,5 @@
32
32
  * ```
33
33
  */
34
34
  export { BehaviorDetector } from './behavior-detector.js';
35
- export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, } from './strategies/index.js';
35
+ export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, Timing, Visibility, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, TimingStrategy, VisibilityStrategy, } from './strategies/index.js';
36
36
  export { DEFAULT_SETTINGS } from './types.js';
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Click Behavior Detection Strategy
3
3
  * Monitors specific elements for click positioning patterns
4
+ *
5
+ * Enhanced detection:
6
+ * - Click-mouse position delta (bots using .click() have mismatched positions)
7
+ * - Pre-click mouse trajectory analysis
8
+ * - Time between last mousemove and click
4
9
  */
5
10
  import { BaseStrategy } from '../strategy.js';
6
11
  export declare class ClickStrategy extends BaseStrategy {
@@ -35,5 +40,8 @@ export declare class ClickStrategy extends BaseStrategy {
35
40
  };
36
41
  inViewport: number;
37
42
  trackedElements: number;
43
+ mouseClickDeltas: number[];
44
+ timeSinceMouseMoves: number[];
45
+ untrustedClicks: number;
38
46
  };
39
47
  }
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Click Behavior Detection Strategy
3
3
  * Monitors specific elements for click positioning patterns
4
+ *
5
+ * Enhanced detection:
6
+ * - Click-mouse position delta (bots using .click() have mismatched positions)
7
+ * - Pre-click mouse trajectory analysis
8
+ * - Time between last mousemove and click
4
9
  */
5
10
  import { BaseStrategy } from '../strategy.js';
6
11
  export class ClickStrategy extends BaseStrategy {
@@ -22,12 +27,13 @@ export class ClickStrategy extends BaseStrategy {
22
27
  if (this.isActive)
23
28
  return;
24
29
  this.isActive = true;
25
- // Track mouse position globally
30
+ // Track mouse position globally with timestamp
26
31
  this.mouseListener = (e) => {
27
32
  const mouseEvent = e;
28
33
  this.lastMousePosition = {
29
34
  x: mouseEvent.clientX,
30
35
  y: mouseEvent.clientY,
36
+ timestamp: Date.now(),
31
37
  };
32
38
  };
33
39
  document.addEventListener('mousemove', this.mouseListener, { passive: true });
@@ -59,12 +65,23 @@ export class ClickStrategy extends BaseStrategy {
59
65
  const listener = (e) => {
60
66
  var _a, _b;
61
67
  const clickEvent = e;
68
+ const now = Date.now();
62
69
  const rect = element.getBoundingClientRect();
63
70
  // Check if element is in viewport
64
71
  const inViewport = (rect.top >= 0 &&
65
72
  rect.left >= 0 &&
66
73
  rect.bottom <= window.innerHeight &&
67
74
  rect.right <= window.innerWidth);
75
+ // Calculate mouse-click position delta
76
+ // Bots using element.click() have click event coordinates different from mouse position
77
+ let mouseClickDelta = 0;
78
+ let timeSinceMouseMove = -1;
79
+ if (this.lastMousePosition) {
80
+ const dx = clickEvent.clientX - this.lastMousePosition.x;
81
+ const dy = clickEvent.clientY - this.lastMousePosition.y;
82
+ mouseClickDelta = Math.sqrt(dx * dx + dy * dy);
83
+ timeSinceMouseMove = now - this.lastMousePosition.timestamp;
84
+ }
68
85
  // Analyze click position
69
86
  let position;
70
87
  if (!this.lastMousePosition) {
@@ -103,7 +120,10 @@ export class ClickStrategy extends BaseStrategy {
103
120
  rect,
104
121
  inViewport,
105
122
  position,
106
- timestamp: Date.now(),
123
+ timestamp: now,
124
+ mouseClickDelta,
125
+ timeSinceMouseMove,
126
+ isTrusted: clickEvent.isTrusted,
107
127
  });
108
128
  // Notify detector - clicks are high-value events
109
129
  this.notifyEvent(1.0);
@@ -132,26 +152,102 @@ export class ClickStrategy extends BaseStrategy {
132
152
  this.lastMousePosition = null;
133
153
  }
134
154
  score() {
135
- if (this.events.length === 0)
155
+ const eventCount = this.events.length;
156
+ if (eventCount === 0)
136
157
  return undefined;
137
- let totalScore = 0;
158
+ // Pre-compute counts in a single pass
159
+ let positionScore = 0;
160
+ let clicksWithDelta = 0;
161
+ let mismatchCount = 0;
162
+ let clicksWithTiming = 0;
163
+ let suspiciousTimingCount = 0;
164
+ let untrustedCount = 0;
138
165
  for (const click of this.events) {
166
+ // Position scoring
139
167
  switch (click.position) {
140
168
  case 'no-mouse-data':
141
- totalScore += 0.0; // Bot - no mouse movement
169
+ positionScore += 0.0;
142
170
  break;
143
171
  case 'outside':
144
- totalScore += 0.0; // Bot - mouse not over target
172
+ positionScore += 0.0;
145
173
  break;
146
174
  case 'dead-center':
147
- totalScore += 0.5; // Suspicious - too perfect
175
+ positionScore += 0.5;
148
176
  break;
149
177
  case 'over-element':
150
- totalScore += 1.0; // Human - natural click
178
+ positionScore += 1.0;
151
179
  break;
152
180
  }
181
+ // Delta scoring
182
+ if (click.mouseClickDelta >= 0) {
183
+ clicksWithDelta++;
184
+ if (click.mouseClickDelta > 5)
185
+ mismatchCount++;
186
+ }
187
+ // Timing scoring
188
+ if (click.timeSinceMouseMove >= 0) {
189
+ clicksWithTiming++;
190
+ if (click.timeSinceMouseMove < 10 || click.timeSinceMouseMove > 2000) {
191
+ suspiciousTimingCount++;
192
+ }
193
+ }
194
+ // Trusted scoring
195
+ if (!click.isTrusted)
196
+ untrustedCount++;
197
+ }
198
+ let score = 0;
199
+ let factors = 0;
200
+ // 1. Position-based scoring
201
+ score += positionScore / eventCount;
202
+ factors++;
203
+ // 2. Mouse-click delta scoring
204
+ if (clicksWithDelta >= 2) {
205
+ const mismatchRatio = mismatchCount / clicksWithDelta;
206
+ let deltaScore;
207
+ if (mismatchRatio >= 0.8)
208
+ deltaScore = 0.1;
209
+ else if (mismatchRatio >= 0.5)
210
+ deltaScore = 0.3;
211
+ else if (mismatchRatio >= 0.3)
212
+ deltaScore = 0.5;
213
+ else if (mismatchRatio > 0)
214
+ deltaScore = 0.7;
215
+ else
216
+ deltaScore = 1.0;
217
+ score += deltaScore;
218
+ factors++;
219
+ }
220
+ // 3. Mouse-to-click timing
221
+ if (clicksWithTiming >= 2) {
222
+ const suspiciousRatio = suspiciousTimingCount / clicksWithTiming;
223
+ let timingScore;
224
+ if (suspiciousRatio >= 0.8)
225
+ timingScore = 0.2;
226
+ else if (suspiciousRatio >= 0.5)
227
+ timingScore = 0.4;
228
+ else if (suspiciousRatio >= 0.3)
229
+ timingScore = 0.6;
230
+ else if (suspiciousRatio > 0)
231
+ timingScore = 0.8;
232
+ else
233
+ timingScore = 1.0;
234
+ score += timingScore;
235
+ factors++;
236
+ }
237
+ // 4. Trusted event scoring
238
+ if (eventCount >= 2) {
239
+ const untrustedRatio = untrustedCount / eventCount;
240
+ let trustedScore;
241
+ if (untrustedRatio >= 0.5)
242
+ trustedScore = 0.1;
243
+ else if (untrustedRatio > 0)
244
+ trustedScore = 0.3;
245
+ else
246
+ trustedScore = 1.0;
247
+ score += trustedScore;
248
+ factors++;
153
249
  }
154
- return totalScore / this.events.length;
250
+ return factors > 0 ? score / factors : undefined;
155
251
  }
156
252
  getDebugInfo() {
157
253
  return {
@@ -164,6 +260,9 @@ export class ClickStrategy extends BaseStrategy {
164
260
  },
165
261
  inViewport: this.events.filter(e => e.inViewport).length,
166
262
  trackedElements: this.clickListeners.size,
263
+ mouseClickDeltas: this.events.map(e => e.mouseClickDelta),
264
+ timeSinceMouseMoves: this.events.map(e => e.timeSinceMouseMove),
265
+ untrustedClicks: this.events.filter(e => !e.isTrusted).length,
167
266
  };
168
267
  }
169
268
  }
@@ -1,8 +1,29 @@
1
1
  /**
2
2
  * Environment Fingerprinting Strategy
3
3
  * Captures browser environment on initialization and tick updates
4
+ * Includes critical automation/bot detection signals
4
5
  */
5
6
  import { BaseStrategy } from '../strategy.js';
7
+ interface AutomationIndicators {
8
+ /** navigator.webdriver === true (most reliable indicator) */
9
+ isWebdriver: boolean;
10
+ /** HeadlessChrome or Headless in user agent */
11
+ hasHeadlessUA: boolean;
12
+ /** window.chrome exists but window.chrome.runtime is undefined */
13
+ hasChromelessRuntime: boolean;
14
+ /** WebGL renderer is SwiftShader or software renderer */
15
+ hasSoftwareRenderer: boolean;
16
+ /** No plugins on desktop browser (suspicious) */
17
+ hasNoPlugins: boolean;
18
+ /** Automation framework globals detected */
19
+ hasAutomationGlobals: boolean;
20
+ /** List of detected automation globals */
21
+ detectedGlobals: string[];
22
+ /** CDP (Chrome DevTools Protocol) injection detected */
23
+ hasCDPInjection: boolean;
24
+ /** Detected CDP injection keys */
25
+ detectedCDPKeys: string[];
26
+ }
6
27
  interface EnvironmentData {
7
28
  screenWidth: number;
8
29
  screenHeight: number;
@@ -29,6 +50,10 @@ interface EnvironmentData {
29
50
  featureInconsistency: boolean;
30
51
  isMobile: boolean;
31
52
  timestamp: number;
53
+ /** Automation/bot detection indicators */
54
+ automation: AutomationIndicators;
55
+ /** WebGL renderer string (for debugging) */
56
+ webglRenderer?: string;
32
57
  }
33
58
  export declare class EnvironmentStrategy extends BaseStrategy {
34
59
  readonly name = "environment";
@@ -40,6 +65,10 @@ export declare class EnvironmentStrategy extends BaseStrategy {
40
65
  onTick(_timestamp: number): void;
41
66
  score(): number | undefined;
42
67
  private isMobileDevice;
68
+ /**
69
+ * Detect automation indicators
70
+ */
71
+ private detectAutomation;
43
72
  private captureEnvironment;
44
73
  getDebugInfo(): EnvironmentData | null;
45
74
  /**
@@ -48,5 +77,15 @@ export declare class EnvironmentStrategy extends BaseStrategy {
48
77
  * Returns null if environment hasn't been captured yet
49
78
  */
50
79
  isMobile(): boolean | null;
80
+ /**
81
+ * Get automation detection results
82
+ * Returns null if environment hasn't been captured yet
83
+ */
84
+ getAutomationIndicators(): AutomationIndicators | null;
85
+ /**
86
+ * Quick check if any strong automation indicator is present
87
+ * Returns true if likely a bot, false if likely human, null if not checked yet
88
+ */
89
+ isLikelyBot(): boolean | null;
51
90
  }
52
91
  export {};
@@ -1,9 +1,75 @@
1
1
  /**
2
2
  * Environment Fingerprinting Strategy
3
3
  * Captures browser environment on initialization and tick updates
4
+ * Includes critical automation/bot detection signals
4
5
  */
5
6
  import { BaseStrategy } from '../strategy.js';
6
7
  import { inverseSigmoid, gaussian } from '../math-utils.js';
8
+ /**
9
+ * Automation framework globals that indicate bot activity
10
+ * These are injected by various testing/automation frameworks
11
+ */
12
+ const AUTOMATION_GLOBALS = [
13
+ '__nightmare',
14
+ '_phantom',
15
+ 'callPhantom',
16
+ '__selenium_evaluate',
17
+ '__webdriver_evaluate',
18
+ '__driver_evaluate',
19
+ '__webdriver_script_function',
20
+ '__lastWatirAlert',
21
+ '__lastWatirConfirm',
22
+ '__lastWatirPrompt',
23
+ '_Selenium_IDE_Recorder',
24
+ 'domAutomation',
25
+ 'domAutomationController',
26
+ 'webdriver',
27
+ '_webdriver_script_fn',
28
+ '__webdriver_script_func',
29
+ '__fxdriver_evaluate',
30
+ '__fxdriver_unwrapped',
31
+ '__selenium_unwrapped',
32
+ ];
33
+ /**
34
+ * Get the WebGL renderer string without triggering deprecation warnings.
35
+ * Modern browsers (Firefox 128+, Chrome 113+) return the unmasked renderer
36
+ * directly from gl.RENDERER. Falls back to the deprecated
37
+ * WEBGL_debug_renderer_info extension for older browsers.
38
+ */
39
+ function getWebGLRenderer(gl) {
40
+ // Modern browsers: gl.RENDERER now returns the unmasked string directly
41
+ const renderer = gl.getParameter(gl.RENDERER);
42
+ if (renderer && !/^(WebKit WebGL|Mozilla|Google Inc\.)$/i.test(renderer)) {
43
+ return renderer;
44
+ }
45
+ // Fallback for older browsers that return a generic RENDERER string
46
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
47
+ if (debugInfo) {
48
+ return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
49
+ }
50
+ return renderer !== null && renderer !== void 0 ? renderer : undefined;
51
+ }
52
+ /**
53
+ * Create a temporary WebGL context, run a callback, then release it.
54
+ */
55
+ function withWebGLContext(fn) {
56
+ try {
57
+ const canvas = document.createElement('canvas');
58
+ const gl = (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
59
+ if (!gl)
60
+ return { hasWebGL: false, result: undefined };
61
+ try {
62
+ return { hasWebGL: true, result: fn(gl) };
63
+ }
64
+ finally {
65
+ const loseCtx = gl.getExtension('WEBGL_lose_context');
66
+ loseCtx === null || loseCtx === void 0 ? void 0 : loseCtx.loseContext();
67
+ }
68
+ }
69
+ catch (_a) {
70
+ return { hasWebGL: false, result: undefined };
71
+ }
72
+ }
7
73
  export class EnvironmentStrategy extends BaseStrategy {
8
74
  constructor() {
9
75
  super(...arguments);
@@ -21,13 +87,50 @@ export class EnvironmentStrategy extends BaseStrategy {
21
87
  this.data = null;
22
88
  }
23
89
  onTick(_timestamp) {
24
- // Re-capture environment periodically to detect changes
25
- this.captureEnvironment();
90
+ // Only capture once - environment data is static and creating
91
+ // WebGL contexts on every tick leaks GPU resources
92
+ if (!this.data) {
93
+ this.captureEnvironment();
94
+ }
26
95
  }
27
96
  score() {
28
97
  if (!this.data)
29
98
  return undefined;
30
99
  const env = this.data;
100
+ const auto = env.automation;
101
+ // ============================================
102
+ // IMMEDIATE DISQUALIFICATION - HIGH CONFIDENCE BOT SIGNALS
103
+ // ============================================
104
+ // P0: navigator.webdriver === true (HIGHEST PRIORITY)
105
+ // This is the most reliable automation indicator
106
+ if (auto.isWebdriver) {
107
+ return 0.05;
108
+ }
109
+ // P0: Automation framework globals detected
110
+ if (auto.hasAutomationGlobals) {
111
+ return 0.1;
112
+ }
113
+ // P0: CDP injection detected (Chrome DevTools Protocol)
114
+ if (auto.hasCDPInjection) {
115
+ return 0.1;
116
+ }
117
+ // P2: Headless browser detection
118
+ // Multiple headless indicators = very likely bot
119
+ const headlessIndicatorCount = [
120
+ auto.hasHeadlessUA,
121
+ auto.hasChromelessRuntime,
122
+ auto.hasSoftwareRenderer,
123
+ ].filter(Boolean).length;
124
+ if (headlessIndicatorCount >= 2) {
125
+ return 0.15;
126
+ }
127
+ // Single strong headless indicator
128
+ if (auto.hasHeadlessUA) {
129
+ return 0.2;
130
+ }
131
+ // ============================================
132
+ // WEIGHTED SCORING FOR OTHER SIGNALS
133
+ // ============================================
31
134
  let score = 0;
32
135
  let factors = 0;
33
136
  // Suspicious indicators
@@ -44,10 +147,27 @@ export class EnvironmentStrategy extends BaseStrategy {
44
147
  ].filter(Boolean).length;
45
148
  score += featureCount / 4;
46
149
  factors++;
47
- // Plugins
48
- score += inverseSigmoid(env.plugins, -2, -0.5);
49
- score += (env.plugins > 0 ? 1.0 : 0.1);
50
- factors += 2;
150
+ // Plugins (only suspicious on desktop)
151
+ if (!env.isMobile) {
152
+ score += inverseSigmoid(env.plugins, -2, -0.5);
153
+ score += (env.plugins > 0 ? 1.0 : 0.1);
154
+ factors += 2;
155
+ // No plugins on desktop Chrome = suspicious (but not conclusive)
156
+ if (auto.hasNoPlugins && env.vendor.includes('Google')) {
157
+ score += 0.3;
158
+ factors++;
159
+ }
160
+ }
161
+ // Chromeless runtime (window.chrome but no chrome.runtime)
162
+ if (auto.hasChromelessRuntime) {
163
+ score += 0.3;
164
+ factors++;
165
+ }
166
+ // Software renderer (SwiftShader)
167
+ if (auto.hasSoftwareRenderer) {
168
+ score += 0.4;
169
+ factors++;
170
+ }
51
171
  // Device
52
172
  score += gaussian(env.devicePixelRatio, 2, 1.5);
53
173
  score += (env.colorDepth === 24 || env.colorDepth === 32) ? 1.0 : 0.4;
@@ -61,17 +181,74 @@ export class EnvironmentStrategy extends BaseStrategy {
61
181
  const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
62
182
  return (hasTouchScreen && smallScreen) || mobileUA;
63
183
  }
184
+ /**
185
+ * Detect automation indicators
186
+ */
187
+ detectAutomation() {
188
+ const isMobile = this.isMobileDevice();
189
+ // 1. navigator.webdriver - HIGHEST PRIORITY
190
+ const isWebdriver = navigator.webdriver === true;
191
+ // 2. Headless UA detection
192
+ const ua = navigator.userAgent;
193
+ const hasHeadlessUA = /HeadlessChrome|Headless/i.test(ua);
194
+ // 3. Chrome without runtime (headless Chrome indicator)
195
+ const win = window;
196
+ const hasChromelessRuntime = !!(win.chrome &&
197
+ typeof win.chrome === 'object' &&
198
+ !win.chrome.runtime);
199
+ // 4. Automation framework globals
200
+ const detectedGlobals = [];
201
+ for (const global of AUTOMATION_GLOBALS) {
202
+ if (global in win) {
203
+ detectedGlobals.push(global);
204
+ }
205
+ }
206
+ const hasAutomationGlobals = detectedGlobals.length > 0;
207
+ // 5. CDP (Chrome DevTools Protocol) injection detection
208
+ // CDP injects properties matching pattern: cdc_ or __cdc_
209
+ const detectedCDPKeys = [];
210
+ try {
211
+ for (const key of Object.keys(win)) {
212
+ if (/^cdc_|^__cdc_/.test(key)) {
213
+ detectedCDPKeys.push(key);
214
+ }
215
+ }
216
+ }
217
+ catch (_a) {
218
+ // Some environments throw on Object.keys(window)
219
+ }
220
+ const hasCDPInjection = detectedCDPKeys.length > 0;
221
+ // 6. No plugins on desktop (suspicious)
222
+ const hasNoPlugins = !isMobile && navigator.plugins.length === 0;
223
+ // 7. Software renderer detection (SwiftShader, ANGLE without GPU)
224
+ let hasSoftwareRenderer = false;
225
+ const webglResult = withWebGLContext((gl) => getWebGLRenderer(gl));
226
+ if (webglResult.hasWebGL && webglResult.result) {
227
+ const renderer = webglResult.result;
228
+ // SwiftShader = headless Chrome software renderer
229
+ // ANGLE without real GPU = suspicious
230
+ hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
231
+ // Store for debugging
232
+ this._webglRenderer = renderer;
233
+ }
234
+ return {
235
+ isWebdriver,
236
+ hasHeadlessUA,
237
+ hasChromelessRuntime,
238
+ hasSoftwareRenderer,
239
+ hasNoPlugins,
240
+ hasAutomationGlobals,
241
+ detectedGlobals,
242
+ hasCDPInjection,
243
+ detectedCDPKeys,
244
+ };
245
+ }
64
246
  captureEnvironment() {
65
247
  try {
66
248
  // WebGL detection
67
- let hasWebGL = false;
68
- try {
69
- const canvas = document.createElement('canvas');
70
- hasWebGL = !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
71
- }
72
- catch (e) {
73
- hasWebGL = false;
74
- }
249
+ const webgl = withWebGLContext((gl) => getWebGLRenderer(gl));
250
+ const hasWebGL = webgl.hasWebGL;
251
+ const webglRenderer = webgl.result;
75
252
  // WebRTC detection
76
253
  const hasWebRTC = !!(window.RTCPeerConnection ||
77
254
  window.mozRTCPeerConnection ||
@@ -97,6 +274,8 @@ export class EnvironmentStrategy extends BaseStrategy {
97
274
  const featureInconsistency = (navigator.plugins.length === 0 && navigator.mimeTypes.length === 0) ||
98
275
  !hasWebGL ||
99
276
  !hasStorage;
277
+ // Detect automation indicators
278
+ const automation = this.detectAutomation();
100
279
  this.data = {
101
280
  screenWidth,
102
281
  screenHeight,
@@ -123,10 +302,12 @@ export class EnvironmentStrategy extends BaseStrategy {
123
302
  featureInconsistency,
124
303
  isMobile,
125
304
  timestamp: Date.now(),
305
+ automation,
306
+ webglRenderer,
126
307
  };
127
308
  }
128
- catch (error) {
129
- console.warn('Failed to capture environment:', error);
309
+ catch (_a) {
310
+ // Silent fail - environment capture is best effort
130
311
  }
131
312
  }
132
313
  getDebugInfo() {
@@ -141,4 +322,25 @@ export class EnvironmentStrategy extends BaseStrategy {
141
322
  var _a, _b;
142
323
  return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.isMobile) !== null && _b !== void 0 ? _b : null;
143
324
  }
325
+ /**
326
+ * Get automation detection results
327
+ * Returns null if environment hasn't been captured yet
328
+ */
329
+ getAutomationIndicators() {
330
+ var _a, _b;
331
+ return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.automation) !== null && _b !== void 0 ? _b : null;
332
+ }
333
+ /**
334
+ * Quick check if any strong automation indicator is present
335
+ * Returns true if likely a bot, false if likely human, null if not checked yet
336
+ */
337
+ isLikelyBot() {
338
+ if (!this.data)
339
+ return null;
340
+ const auto = this.data.automation;
341
+ return (auto.isWebdriver ||
342
+ auto.hasAutomationGlobals ||
343
+ auto.hasCDPInjection ||
344
+ auto.hasHeadlessUA);
345
+ }
144
346
  }
@@ -9,6 +9,8 @@ export { TapStrategy } from './tap.js';
9
9
  export { KeyboardStrategy } from './keyboard.js';
10
10
  export { EnvironmentStrategy } from './environment.js';
11
11
  export { ResizeStrategy } from './resize.js';
12
+ export { TimingStrategy } from './timing.js';
13
+ export { VisibilityStrategy } from './visibility.js';
12
14
  export { MouseStrategy as Mouse } from './mouse.js';
13
15
  export { ScrollStrategy as Scroll } from './scroll.js';
14
16
  export { ClickStrategy as Click } from './click.js';
@@ -16,3 +18,5 @@ export { TapStrategy as Tap } from './tap.js';
16
18
  export { KeyboardStrategy as Keyboard } from './keyboard.js';
17
19
  export { EnvironmentStrategy as Environment } from './environment.js';
18
20
  export { ResizeStrategy as Resize } from './resize.js';
21
+ export { TimingStrategy as Timing } from './timing.js';
22
+ export { VisibilityStrategy as Visibility } from './visibility.js';
@@ -9,6 +9,8 @@ export { TapStrategy } from './tap.js';
9
9
  export { KeyboardStrategy } from './keyboard.js';
10
10
  export { EnvironmentStrategy } from './environment.js';
11
11
  export { ResizeStrategy } from './resize.js';
12
+ export { TimingStrategy } from './timing.js';
13
+ export { VisibilityStrategy } from './visibility.js';
12
14
  // Convenience: All strategies
13
15
  export { MouseStrategy as Mouse } from './mouse.js';
14
16
  export { ScrollStrategy as Scroll } from './scroll.js';
@@ -17,3 +19,5 @@ export { TapStrategy as Tap } from './tap.js';
17
19
  export { KeyboardStrategy as Keyboard } from './keyboard.js';
18
20
  export { EnvironmentStrategy as Environment } from './environment.js';
19
21
  export { ResizeStrategy as Resize } from './resize.js';
22
+ export { TimingStrategy as Timing } from './timing.js';
23
+ export { VisibilityStrategy as Visibility } from './visibility.js';