@axeptio/behavior-detection 1.0.3 → 1.1.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.
@@ -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,35 @@
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
+ ];
7
33
  export class EnvironmentStrategy extends BaseStrategy {
8
34
  constructor() {
9
35
  super(...arguments);
@@ -28,6 +54,40 @@ export class EnvironmentStrategy extends BaseStrategy {
28
54
  if (!this.data)
29
55
  return undefined;
30
56
  const env = this.data;
57
+ const auto = env.automation;
58
+ // ============================================
59
+ // IMMEDIATE DISQUALIFICATION - HIGH CONFIDENCE BOT SIGNALS
60
+ // ============================================
61
+ // P0: navigator.webdriver === true (HIGHEST PRIORITY)
62
+ // This is the most reliable automation indicator
63
+ if (auto.isWebdriver) {
64
+ return 0.05;
65
+ }
66
+ // P0: Automation framework globals detected
67
+ if (auto.hasAutomationGlobals) {
68
+ return 0.1;
69
+ }
70
+ // P0: CDP injection detected (Chrome DevTools Protocol)
71
+ if (auto.hasCDPInjection) {
72
+ return 0.1;
73
+ }
74
+ // P2: Headless browser detection
75
+ // Multiple headless indicators = very likely bot
76
+ const headlessIndicatorCount = [
77
+ auto.hasHeadlessUA,
78
+ auto.hasChromelessRuntime,
79
+ auto.hasSoftwareRenderer,
80
+ ].filter(Boolean).length;
81
+ if (headlessIndicatorCount >= 2) {
82
+ return 0.15;
83
+ }
84
+ // Single strong headless indicator
85
+ if (auto.hasHeadlessUA) {
86
+ return 0.2;
87
+ }
88
+ // ============================================
89
+ // WEIGHTED SCORING FOR OTHER SIGNALS
90
+ // ============================================
31
91
  let score = 0;
32
92
  let factors = 0;
33
93
  // Suspicious indicators
@@ -44,10 +104,27 @@ export class EnvironmentStrategy extends BaseStrategy {
44
104
  ].filter(Boolean).length;
45
105
  score += featureCount / 4;
46
106
  factors++;
47
- // Plugins
48
- score += inverseSigmoid(env.plugins, -2, -0.5);
49
- score += (env.plugins > 0 ? 1.0 : 0.1);
50
- factors += 2;
107
+ // Plugins (only suspicious on desktop)
108
+ if (!env.isMobile) {
109
+ score += inverseSigmoid(env.plugins, -2, -0.5);
110
+ score += (env.plugins > 0 ? 1.0 : 0.1);
111
+ factors += 2;
112
+ // No plugins on desktop Chrome = suspicious (but not conclusive)
113
+ if (auto.hasNoPlugins && env.vendor.includes('Google')) {
114
+ score += 0.3;
115
+ factors++;
116
+ }
117
+ }
118
+ // Chromeless runtime (window.chrome but no chrome.runtime)
119
+ if (auto.hasChromelessRuntime) {
120
+ score += 0.3;
121
+ factors++;
122
+ }
123
+ // Software renderer (SwiftShader)
124
+ if (auto.hasSoftwareRenderer) {
125
+ score += 0.4;
126
+ factors++;
127
+ }
51
128
  // Device
52
129
  score += gaussian(env.devicePixelRatio, 2, 1.5);
53
130
  score += (env.colorDepth === 24 || env.colorDepth === 32) ? 1.0 : 0.4;
@@ -61,13 +138,92 @@ export class EnvironmentStrategy extends BaseStrategy {
61
138
  const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
62
139
  return (hasTouchScreen && smallScreen) || mobileUA;
63
140
  }
141
+ /**
142
+ * Detect automation indicators
143
+ */
144
+ detectAutomation() {
145
+ const isMobile = this.isMobileDevice();
146
+ // 1. navigator.webdriver - HIGHEST PRIORITY
147
+ const isWebdriver = navigator.webdriver === true;
148
+ // 2. Headless UA detection
149
+ const ua = navigator.userAgent;
150
+ const hasHeadlessUA = /HeadlessChrome|Headless/i.test(ua);
151
+ // 3. Chrome without runtime (headless Chrome indicator)
152
+ const win = window;
153
+ const hasChromelessRuntime = !!(win.chrome &&
154
+ typeof win.chrome === 'object' &&
155
+ !win.chrome.runtime);
156
+ // 4. Automation framework globals
157
+ const detectedGlobals = [];
158
+ for (const global of AUTOMATION_GLOBALS) {
159
+ if (global in win) {
160
+ detectedGlobals.push(global);
161
+ }
162
+ }
163
+ const hasAutomationGlobals = detectedGlobals.length > 0;
164
+ // 5. CDP (Chrome DevTools Protocol) injection detection
165
+ // CDP injects properties matching pattern: cdc_ or __cdc_
166
+ const detectedCDPKeys = [];
167
+ try {
168
+ for (const key of Object.keys(win)) {
169
+ if (/^cdc_|^__cdc_/.test(key)) {
170
+ detectedCDPKeys.push(key);
171
+ }
172
+ }
173
+ }
174
+ catch (_a) {
175
+ // Some environments throw on Object.keys(window)
176
+ }
177
+ const hasCDPInjection = detectedCDPKeys.length > 0;
178
+ // 6. No plugins on desktop (suspicious)
179
+ const hasNoPlugins = !isMobile && navigator.plugins.length === 0;
180
+ // 7. Software renderer detection (SwiftShader, ANGLE without GPU)
181
+ let hasSoftwareRenderer = false;
182
+ try {
183
+ const canvas = document.createElement('canvas');
184
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
185
+ if (gl) {
186
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
187
+ if (debugInfo) {
188
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
189
+ // SwiftShader = headless Chrome software renderer
190
+ // ANGLE without real GPU = suspicious
191
+ hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
192
+ // Store for debugging
193
+ this._webglRenderer = renderer;
194
+ }
195
+ }
196
+ }
197
+ catch (_b) {
198
+ // WebGL not available
199
+ }
200
+ return {
201
+ isWebdriver,
202
+ hasHeadlessUA,
203
+ hasChromelessRuntime,
204
+ hasSoftwareRenderer,
205
+ hasNoPlugins,
206
+ hasAutomationGlobals,
207
+ detectedGlobals,
208
+ hasCDPInjection,
209
+ detectedCDPKeys,
210
+ };
211
+ }
64
212
  captureEnvironment() {
65
213
  try {
66
214
  // WebGL detection
67
215
  let hasWebGL = false;
216
+ let webglRenderer;
68
217
  try {
69
218
  const canvas = document.createElement('canvas');
70
- hasWebGL = !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
219
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
220
+ hasWebGL = !!gl;
221
+ if (gl) {
222
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
223
+ if (debugInfo) {
224
+ webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
225
+ }
226
+ }
71
227
  }
72
228
  catch (e) {
73
229
  hasWebGL = false;
@@ -97,6 +253,8 @@ export class EnvironmentStrategy extends BaseStrategy {
97
253
  const featureInconsistency = (navigator.plugins.length === 0 && navigator.mimeTypes.length === 0) ||
98
254
  !hasWebGL ||
99
255
  !hasStorage;
256
+ // Detect automation indicators
257
+ const automation = this.detectAutomation();
100
258
  this.data = {
101
259
  screenWidth,
102
260
  screenHeight,
@@ -123,10 +281,12 @@ export class EnvironmentStrategy extends BaseStrategy {
123
281
  featureInconsistency,
124
282
  isMobile,
125
283
  timestamp: Date.now(),
284
+ automation,
285
+ webglRenderer,
126
286
  };
127
287
  }
128
- catch (error) {
129
- console.warn('Failed to capture environment:', error);
288
+ catch (_a) {
289
+ // Silent fail - environment capture is best effort
130
290
  }
131
291
  }
132
292
  getDebugInfo() {
@@ -141,4 +301,25 @@ export class EnvironmentStrategy extends BaseStrategy {
141
301
  var _a, _b;
142
302
  return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.isMobile) !== null && _b !== void 0 ? _b : null;
143
303
  }
304
+ /**
305
+ * Get automation detection results
306
+ * Returns null if environment hasn't been captured yet
307
+ */
308
+ getAutomationIndicators() {
309
+ var _a, _b;
310
+ return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.automation) !== null && _b !== void 0 ? _b : null;
311
+ }
312
+ /**
313
+ * Quick check if any strong automation indicator is present
314
+ * Returns true if likely a bot, false if likely human, null if not checked yet
315
+ */
316
+ isLikelyBot() {
317
+ if (!this.data)
318
+ return null;
319
+ const auto = this.data.automation;
320
+ return (auto.isWebdriver ||
321
+ auto.hasAutomationGlobals ||
322
+ auto.hasCDPInjection ||
323
+ auto.hasHeadlessUA);
324
+ }
144
325
  }
@@ -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';
@@ -2,8 +2,27 @@
2
2
  * Mouse Movement Detection Strategy
3
3
  * Autonomous module that manages its own mouse event listeners and state
4
4
  * Tracks distance and angle to detect unnatural jumps and sharp turns
5
+ *
6
+ * Enhanced detection:
7
+ * - Pre-click stillness (micro-movements before clicks)
8
+ * - Entry point analysis (where mouse enters viewport)
9
+ * - Velocity consistency
5
10
  */
6
11
  import { BaseStrategy, type TimeSeriesPoint } from '../strategy.js';
12
+ interface MousePosition {
13
+ x: number;
14
+ y: number;
15
+ timestamp: number;
16
+ }
17
+ interface EntryPoint {
18
+ x: number;
19
+ y: number;
20
+ timestamp: number;
21
+ /** Distance from nearest edge (0 = at edge) */
22
+ edgeDistance: number;
23
+ /** Which edge: 'top' | 'bottom' | 'left' | 'right' | 'corner' | 'center' */
24
+ entryEdge: 'top' | 'bottom' | 'left' | 'right' | 'corner' | 'center';
25
+ }
7
26
  export declare class MouseStrategy extends BaseStrategy {
8
27
  readonly name = "mouse";
9
28
  readonly defaultWeight = 0.3;
@@ -15,8 +34,15 @@ export declare class MouseStrategy extends BaseStrategy {
15
34
  private rollingWindowMs;
16
35
  private listener;
17
36
  private leaveListener;
37
+ private enterListener;
18
38
  private isActive;
19
39
  private screenDiagonal;
40
+ /** Entry points tracking */
41
+ private entryPoints;
42
+ /** Micro-movements in last 500ms (for stillness detection) */
43
+ private microMovements;
44
+ /** Stillness window in ms */
45
+ private readonly STILLNESS_WINDOW;
20
46
  constructor(options?: {
21
47
  rollingWindow?: number;
22
48
  });
@@ -26,14 +52,40 @@ export declare class MouseStrategy extends BaseStrategy {
26
52
  score(): number | undefined;
27
53
  /**
28
54
  * Mouse-specific pattern detection
29
- * Detects bot-like patterns: constant velocity, linear paths
55
+ * Detects bot-like patterns: constant velocity, linear paths, suspicious entry points
30
56
  */
31
57
  private detectMousePatterns;
58
+ /**
59
+ * Score based on viewport entry points
60
+ * Humans enter from edges with momentum; bots often start at (0,0) or center
61
+ */
62
+ private scoreEntryPoints;
63
+ /**
64
+ * Score based on micro-movements (tremor detection)
65
+ * Humans have natural hand tremor causing 1-5px jitter
66
+ * Bots have perfect stillness or no micro-movements
67
+ */
68
+ private scoreMicroMovements;
69
+ /**
70
+ * Get micro-movement count for external use (e.g., by ClickStrategy)
71
+ */
72
+ getMicroMovementCount(): number;
73
+ /**
74
+ * Get last position for external use
75
+ */
76
+ getLastPosition(): {
77
+ x: number;
78
+ y: number;
79
+ } | null;
32
80
  getDebugInfo(): {
33
81
  eventCount: number;
34
82
  rollingWindow: number;
35
83
  isActive: boolean;
36
84
  distanceSeries: TimeSeriesPoint[];
37
85
  angleSeries: TimeSeriesPoint[];
86
+ entryPoints: EntryPoint[];
87
+ microMovementCount: number;
88
+ lastPosition: MousePosition | null;
38
89
  };
39
90
  }
91
+ export {};