@axeptio/behavior-detection 1.0.2 → 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.
Files changed (59) hide show
  1. package/README.md +126 -12
  2. package/dist/behavior-detection.esm.min.js +1 -1
  3. package/dist/behavior-detection.esm.min.js.map +4 -4
  4. package/dist/behavior-detection.min.js +1 -1
  5. package/dist/behavior-detection.min.js.map +3 -3
  6. package/dist/cjs/index.cjs +2320 -0
  7. package/dist/esm/browser.js +0 -2
  8. package/dist/esm/index.d.ts +1 -1
  9. package/dist/esm/index.js +1 -1
  10. package/dist/esm/strategies/click.d.ts +8 -0
  11. package/dist/esm/strategies/click.js +108 -9
  12. package/dist/esm/strategies/environment.d.ts +39 -0
  13. package/dist/esm/strategies/environment.js +188 -7
  14. package/dist/esm/strategies/index.d.ts +4 -0
  15. package/dist/esm/strategies/index.js +4 -0
  16. package/dist/esm/strategies/mouse.d.ts +53 -1
  17. package/dist/esm/strategies/mouse.js +198 -2
  18. package/dist/esm/strategies/timing.d.ts +64 -0
  19. package/dist/esm/strategies/timing.js +308 -0
  20. package/dist/esm/strategies/visibility.d.ts +64 -0
  21. package/dist/esm/strategies/visibility.js +295 -0
  22. package/dist/index.d.ts +1 -1
  23. package/dist/strategies/click.d.ts +8 -0
  24. package/dist/strategies/environment.d.ts +39 -0
  25. package/dist/strategies/index.d.ts +4 -0
  26. package/dist/strategies/mouse.d.ts +53 -1
  27. package/dist/strategies/timing.d.ts +64 -0
  28. package/dist/strategies/visibility.d.ts +64 -0
  29. package/package.json +16 -15
  30. package/dist/cjs/behavior-detector.d.ts +0 -102
  31. package/dist/cjs/behavior-detector.js +0 -315
  32. package/dist/cjs/browser.d.ts +0 -33
  33. package/dist/cjs/browser.js +0 -226
  34. package/dist/cjs/index.d.ts +0 -38
  35. package/dist/cjs/index.js +0 -55
  36. package/dist/cjs/math-utils.d.ts +0 -84
  37. package/dist/cjs/math-utils.js +0 -141
  38. package/dist/cjs/strategies/click.d.ts +0 -39
  39. package/dist/cjs/strategies/click.js +0 -173
  40. package/dist/cjs/strategies/environment.d.ts +0 -52
  41. package/dist/cjs/strategies/environment.js +0 -148
  42. package/dist/cjs/strategies/index.d.ts +0 -18
  43. package/dist/cjs/strategies/index.js +0 -36
  44. package/dist/cjs/strategies/keyboard.d.ts +0 -43
  45. package/dist/cjs/strategies/keyboard.js +0 -233
  46. package/dist/cjs/strategies/mouse.d.ts +0 -39
  47. package/dist/cjs/strategies/mouse.js +0 -159
  48. package/dist/cjs/strategies/resize.d.ts +0 -21
  49. package/dist/cjs/strategies/resize.js +0 -97
  50. package/dist/cjs/strategies/scroll.d.ts +0 -37
  51. package/dist/cjs/strategies/scroll.js +0 -149
  52. package/dist/cjs/strategies/tap.d.ts +0 -38
  53. package/dist/cjs/strategies/tap.js +0 -214
  54. package/dist/cjs/strategy.d.ts +0 -107
  55. package/dist/cjs/strategy.js +0 -33
  56. package/dist/cjs/types.d.ts +0 -168
  57. package/dist/cjs/types.js +0 -26
  58. package/dist/esm/browser-iife.d.ts +0 -5
  59. package/dist/esm/browser-iife.js +0 -157
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Visibility Strategy
3
+ * Detects bot-like behavior based on tab visibility and focus patterns
4
+ *
5
+ * Key detection signals:
6
+ * - Actions while document.hidden === true (bots ignore visibility)
7
+ * - No pause when tab loses focus
8
+ * - Activity resumes too quickly after tab regains focus
9
+ * - Focus patterns: humans focus inputs before typing
10
+ */
11
+ import { BaseStrategy } from '../strategy.js';
12
+ export declare class VisibilityStrategy extends BaseStrategy {
13
+ readonly name = "visibility";
14
+ readonly defaultWeight = 0.1;
15
+ private events;
16
+ private focusTypingPairs;
17
+ private actionsWhileHidden;
18
+ private lastVisibilityChange;
19
+ private resumeDelays;
20
+ private isActive;
21
+ private visibilityListener;
22
+ private focusListener;
23
+ private blurListener;
24
+ private clickListener;
25
+ private keydownListener;
26
+ private inputFocusListener;
27
+ private lastFocusedInput;
28
+ private hasTypedInFocusedInput;
29
+ private lastActionTime;
30
+ private preHideActionTime;
31
+ start(): void;
32
+ private recordAction;
33
+ stop(): void;
34
+ reset(): void;
35
+ score(): number | undefined;
36
+ /**
37
+ * Score based on actions while document was hidden
38
+ * Humans can't interact with hidden tabs; bots can
39
+ */
40
+ private scoreHiddenActions;
41
+ /**
42
+ * Score based on how quickly activity resumes after tab becomes visible
43
+ * Humans need time to refocus (100-500ms minimum)
44
+ * Bots often resume instantly (< 50ms)
45
+ */
46
+ private scoreResumeDelays;
47
+ /**
48
+ * Score based on focus-to-keypress timing
49
+ * Humans focus inputs before typing (natural delay 100-500ms)
50
+ * Bots often type without focusing or with instant delay
51
+ */
52
+ private scoreFocusTyping;
53
+ getDebugInfo(): {
54
+ eventCount: number;
55
+ actionsWhileHidden: number;
56
+ visibilityChanges: number;
57
+ focusChanges: number;
58
+ resumeDelays: number[];
59
+ focusTypingPairs: {
60
+ delay: number;
61
+ element: string;
62
+ }[];
63
+ };
64
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Visibility Strategy
3
+ * Detects bot-like behavior based on tab visibility and focus patterns
4
+ *
5
+ * Key detection signals:
6
+ * - Actions while document.hidden === true (bots ignore visibility)
7
+ * - No pause when tab loses focus
8
+ * - Activity resumes too quickly after tab regains focus
9
+ * - Focus patterns: humans focus inputs before typing
10
+ */
11
+ import { BaseStrategy } from '../strategy.js';
12
+ export class VisibilityStrategy extends BaseStrategy {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.name = 'visibility';
16
+ this.defaultWeight = 0.10;
17
+ this.events = [];
18
+ this.focusTypingPairs = [];
19
+ this.actionsWhileHidden = 0;
20
+ this.lastVisibilityChange = null;
21
+ this.resumeDelays = [];
22
+ this.isActive = false;
23
+ // Listeners
24
+ this.visibilityListener = null;
25
+ this.focusListener = null;
26
+ this.blurListener = null;
27
+ this.clickListener = null;
28
+ this.keydownListener = null;
29
+ this.inputFocusListener = null;
30
+ // State
31
+ this.lastFocusedInput = null;
32
+ this.hasTypedInFocusedInput = false;
33
+ this.lastActionTime = 0;
34
+ this.preHideActionTime = 0;
35
+ }
36
+ start() {
37
+ if (this.isActive)
38
+ return;
39
+ this.isActive = true;
40
+ // Track visibility changes
41
+ this.visibilityListener = () => {
42
+ const now = Date.now();
43
+ const wasHidden = document.hidden;
44
+ // Track time between visibility changes
45
+ if (this.lastVisibilityChange && !wasHidden) {
46
+ // Tab just became visible again
47
+ // Check if there was activity right before becoming visible
48
+ // Humans need time to refocus after tab switch
49
+ if (this.lastActionTime > 0 && this.preHideActionTime > 0) {
50
+ const timeSinceLastAction = now - this.lastActionTime;
51
+ // If action was very recent (< 50ms), suspicious
52
+ if (timeSinceLastAction < 50) {
53
+ this.actionsWhileHidden++;
54
+ }
55
+ }
56
+ // Track resume delay (time until first action after visibility)
57
+ // Will be calculated when next action occurs
58
+ this.lastVisibilityChange = { hidden: false, timestamp: now };
59
+ }
60
+ else if (wasHidden) {
61
+ // Tab just became hidden - record pre-hide action time
62
+ this.preHideActionTime = this.lastActionTime;
63
+ this.lastVisibilityChange = { hidden: true, timestamp: now };
64
+ }
65
+ this.events.push({
66
+ type: 'visibility_change',
67
+ timestamp: now,
68
+ wasHidden,
69
+ });
70
+ };
71
+ // Track window focus
72
+ this.focusListener = () => {
73
+ this.events.push({
74
+ type: 'focus_change',
75
+ timestamp: Date.now(),
76
+ wasHidden: document.hidden,
77
+ wasFocused: true,
78
+ });
79
+ };
80
+ this.blurListener = () => {
81
+ this.events.push({
82
+ type: 'focus_change',
83
+ timestamp: Date.now(),
84
+ wasHidden: document.hidden,
85
+ wasFocused: false,
86
+ });
87
+ };
88
+ // Track actions while hidden
89
+ this.clickListener = (e) => {
90
+ this.recordAction('click', e);
91
+ };
92
+ this.keydownListener = (e) => {
93
+ this.recordAction('keydown', e);
94
+ // Track focus-to-keypress timing
95
+ if (this.lastFocusedInput && !this.hasTypedInFocusedInput) {
96
+ const now = Date.now();
97
+ const delay = now - this.lastFocusedInput.timestamp;
98
+ this.focusTypingPairs.push({
99
+ focusTime: this.lastFocusedInput.timestamp,
100
+ firstKeypressTime: now,
101
+ delay,
102
+ element: this.lastFocusedInput.element,
103
+ });
104
+ this.hasTypedInFocusedInput = true;
105
+ }
106
+ };
107
+ // Track input focus for focus-typing analysis
108
+ this.inputFocusListener = (e) => {
109
+ const target = e.target;
110
+ if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')) {
111
+ this.lastFocusedInput = {
112
+ element: target.tagName.toLowerCase(),
113
+ timestamp: Date.now(),
114
+ };
115
+ this.hasTypedInFocusedInput = false;
116
+ }
117
+ };
118
+ document.addEventListener('visibilitychange', this.visibilityListener);
119
+ window.addEventListener('focus', this.focusListener);
120
+ window.addEventListener('blur', this.blurListener);
121
+ document.addEventListener('click', this.clickListener, { passive: true });
122
+ document.addEventListener('keydown', this.keydownListener, { passive: true });
123
+ document.addEventListener('focusin', this.inputFocusListener, { passive: true });
124
+ }
125
+ recordAction(_type, _event) {
126
+ const now = Date.now();
127
+ this.lastActionTime = now;
128
+ // Check if action occurred while document was hidden
129
+ if (document.hidden) {
130
+ this.actionsWhileHidden++;
131
+ this.events.push({
132
+ type: 'action_while_hidden',
133
+ timestamp: now,
134
+ wasHidden: true,
135
+ });
136
+ // Notify detector - this is a strong bot signal
137
+ this.notifyEvent(1.0);
138
+ }
139
+ // Track resume delay after visibility change
140
+ if (this.lastVisibilityChange && !this.lastVisibilityChange.hidden) {
141
+ const resumeDelay = now - this.lastVisibilityChange.timestamp;
142
+ // Only count if this is the first action after becoming visible
143
+ // and the delay is reasonable (< 10 seconds)
144
+ if (resumeDelay > 0 && resumeDelay < 10000 && this.resumeDelays.length < 20) {
145
+ // Check if we haven't already recorded a delay for this visibility change
146
+ const lastRecordedDelay = this.resumeDelays[this.resumeDelays.length - 1];
147
+ if (lastRecordedDelay === undefined || Math.abs(resumeDelay - lastRecordedDelay) > 100) {
148
+ this.resumeDelays.push(resumeDelay);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ stop() {
154
+ if (!this.isActive)
155
+ return;
156
+ this.isActive = false;
157
+ if (this.visibilityListener) {
158
+ document.removeEventListener('visibilitychange', this.visibilityListener);
159
+ this.visibilityListener = null;
160
+ }
161
+ if (this.focusListener) {
162
+ window.removeEventListener('focus', this.focusListener);
163
+ this.focusListener = null;
164
+ }
165
+ if (this.blurListener) {
166
+ window.removeEventListener('blur', this.blurListener);
167
+ this.blurListener = null;
168
+ }
169
+ if (this.clickListener) {
170
+ document.removeEventListener('click', this.clickListener);
171
+ this.clickListener = null;
172
+ }
173
+ if (this.keydownListener) {
174
+ document.removeEventListener('keydown', this.keydownListener);
175
+ this.keydownListener = null;
176
+ }
177
+ if (this.inputFocusListener) {
178
+ document.removeEventListener('focusin', this.inputFocusListener);
179
+ this.inputFocusListener = null;
180
+ }
181
+ }
182
+ reset() {
183
+ this.events = [];
184
+ this.focusTypingPairs = [];
185
+ this.actionsWhileHidden = 0;
186
+ this.lastVisibilityChange = null;
187
+ this.resumeDelays = [];
188
+ this.lastFocusedInput = null;
189
+ this.hasTypedInFocusedInput = false;
190
+ this.lastActionTime = 0;
191
+ this.preHideActionTime = 0;
192
+ }
193
+ score() {
194
+ // Need some visibility events to score
195
+ if (this.events.length < 2)
196
+ return undefined;
197
+ let score = 0;
198
+ let factors = 0;
199
+ // 1. Actions while hidden (strongest signal)
200
+ const hiddenActionScore = this.scoreHiddenActions();
201
+ if (hiddenActionScore !== undefined) {
202
+ score += hiddenActionScore;
203
+ factors++;
204
+ }
205
+ // 2. Resume delay analysis
206
+ const resumeScore = this.scoreResumeDelays();
207
+ if (resumeScore !== undefined) {
208
+ score += resumeScore;
209
+ factors++;
210
+ }
211
+ // 3. Focus-typing pattern
212
+ const focusTypingScore = this.scoreFocusTyping();
213
+ if (focusTypingScore !== undefined) {
214
+ score += focusTypingScore;
215
+ factors++;
216
+ }
217
+ return factors > 0 ? score / factors : undefined;
218
+ }
219
+ /**
220
+ * Score based on actions while document was hidden
221
+ * Humans can't interact with hidden tabs; bots can
222
+ */
223
+ scoreHiddenActions() {
224
+ if (this.actionsWhileHidden === 0)
225
+ return 1.0; // Perfect
226
+ // Any action while hidden is highly suspicious
227
+ if (this.actionsWhileHidden >= 5)
228
+ return 0.1;
229
+ if (this.actionsWhileHidden >= 3)
230
+ return 0.2;
231
+ if (this.actionsWhileHidden >= 1)
232
+ return 0.3;
233
+ return 1.0;
234
+ }
235
+ /**
236
+ * Score based on how quickly activity resumes after tab becomes visible
237
+ * Humans need time to refocus (100-500ms minimum)
238
+ * Bots often resume instantly (< 50ms)
239
+ */
240
+ scoreResumeDelays() {
241
+ if (this.resumeDelays.length < 2)
242
+ return undefined;
243
+ // Count suspiciously fast resumes (< 50ms)
244
+ const fastResumeCount = this.resumeDelays.filter(d => d < 50).length;
245
+ const fastResumeRatio = fastResumeCount / this.resumeDelays.length;
246
+ if (fastResumeRatio >= 0.8)
247
+ return 0.1;
248
+ if (fastResumeRatio >= 0.5)
249
+ return 0.3;
250
+ if (fastResumeRatio >= 0.3)
251
+ return 0.5;
252
+ if (fastResumeRatio > 0)
253
+ return 0.7;
254
+ return 1.0;
255
+ }
256
+ /**
257
+ * Score based on focus-to-keypress timing
258
+ * Humans focus inputs before typing (natural delay 100-500ms)
259
+ * Bots often type without focusing or with instant delay
260
+ */
261
+ scoreFocusTyping() {
262
+ if (this.focusTypingPairs.length < 2)
263
+ return undefined;
264
+ let suspiciousCount = 0;
265
+ for (const pair of this.focusTypingPairs) {
266
+ // Instant typing after focus (< 20ms) is suspicious
267
+ if (pair.delay < 20) {
268
+ suspiciousCount++;
269
+ }
270
+ }
271
+ const suspiciousRatio = suspiciousCount / this.focusTypingPairs.length;
272
+ if (suspiciousRatio >= 0.8)
273
+ return 0.2;
274
+ if (suspiciousRatio >= 0.5)
275
+ return 0.4;
276
+ if (suspiciousRatio >= 0.3)
277
+ return 0.6;
278
+ if (suspiciousRatio > 0)
279
+ return 0.8;
280
+ return 1.0;
281
+ }
282
+ getDebugInfo() {
283
+ return {
284
+ eventCount: this.events.length,
285
+ actionsWhileHidden: this.actionsWhileHidden,
286
+ visibilityChanges: this.events.filter(e => e.type === 'visibility_change').length,
287
+ focusChanges: this.events.filter(e => e.type === 'focus_change').length,
288
+ resumeDelays: this.resumeDelays,
289
+ focusTypingPairs: this.focusTypingPairs.map(p => ({
290
+ delay: p.delay,
291
+ element: p.element,
292
+ })),
293
+ };
294
+ }
295
+ }
package/dist/index.d.ts CHANGED
@@ -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';
@@ -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,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 {};
@@ -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';
@@ -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 {};
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Timing Analysis Strategy
3
+ * Detects bot-like timing patterns in user interactions
4
+ *
5
+ * Key detection signals:
6
+ * - Pre-action stillness (humans have micro-movements before clicking)
7
+ * - Action timing patterns (machine-precise intervals)
8
+ * - Inactivity patterns (bursts after long silence, no idle variance)
9
+ * - Actions while tab is hidden (bots ignore visibility)
10
+ */
11
+ import { BaseStrategy } from '../strategy.js';
12
+ export declare class TimingStrategy extends BaseStrategy {
13
+ readonly name = "timing";
14
+ readonly defaultWeight = 0.15;
15
+ private actions;
16
+ private mouseStillness;
17
+ private microMovements;
18
+ private isActive;
19
+ private mouseListener;
20
+ private clickListener;
21
+ private keydownListener;
22
+ private scrollListener;
23
+ /** Time window to consider for pre-action stillness (ms) */
24
+ private readonly STILLNESS_WINDOW;
25
+ /** Minimum distance to count as meaningful movement (px) */
26
+ private readonly MICRO_MOVEMENT_THRESHOLD;
27
+ /** Maximum micro-movement to count (jitter vs intentional) (px) */
28
+ private readonly MAX_MICRO_MOVEMENT;
29
+ /** Machine-precision threshold (intervals within this are suspicious) */
30
+ private readonly MACHINE_PRECISION_THRESHOLD;
31
+ start(): void;
32
+ private recordAction;
33
+ stop(): void;
34
+ reset(): void;
35
+ score(): number | undefined;
36
+ /**
37
+ * Score based on pre-action stillness
38
+ * Humans have micro-movements before clicking; bots are perfectly still
39
+ */
40
+ private scorePreActionStillness;
41
+ /**
42
+ * Score mouse-to-click delay patterns (using pre-filtered cache)
43
+ */
44
+ private scoreMouseToClickDelayFromCache;
45
+ /**
46
+ * Score action intervals for machine-like precision
47
+ * Detects exact intervals: 100ms, 500ms, 1000ms
48
+ */
49
+ private scoreActionIntervals;
50
+ /**
51
+ * Score based on actions while document was hidden (using pre-computed count)
52
+ */
53
+ private scoreHiddenActionsFromCount;
54
+ getDebugInfo(): {
55
+ actionCount: number;
56
+ clickCount: number;
57
+ keydownCount: number;
58
+ scrollCount: number;
59
+ hiddenActionCount: number;
60
+ microMovementCount: number;
61
+ intervals: number[];
62
+ lastStillnessDuration: number | null;
63
+ };
64
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Visibility Strategy
3
+ * Detects bot-like behavior based on tab visibility and focus patterns
4
+ *
5
+ * Key detection signals:
6
+ * - Actions while document.hidden === true (bots ignore visibility)
7
+ * - No pause when tab loses focus
8
+ * - Activity resumes too quickly after tab regains focus
9
+ * - Focus patterns: humans focus inputs before typing
10
+ */
11
+ import { BaseStrategy } from '../strategy.js';
12
+ export declare class VisibilityStrategy extends BaseStrategy {
13
+ readonly name = "visibility";
14
+ readonly defaultWeight = 0.1;
15
+ private events;
16
+ private focusTypingPairs;
17
+ private actionsWhileHidden;
18
+ private lastVisibilityChange;
19
+ private resumeDelays;
20
+ private isActive;
21
+ private visibilityListener;
22
+ private focusListener;
23
+ private blurListener;
24
+ private clickListener;
25
+ private keydownListener;
26
+ private inputFocusListener;
27
+ private lastFocusedInput;
28
+ private hasTypedInFocusedInput;
29
+ private lastActionTime;
30
+ private preHideActionTime;
31
+ start(): void;
32
+ private recordAction;
33
+ stop(): void;
34
+ reset(): void;
35
+ score(): number | undefined;
36
+ /**
37
+ * Score based on actions while document was hidden
38
+ * Humans can't interact with hidden tabs; bots can
39
+ */
40
+ private scoreHiddenActions;
41
+ /**
42
+ * Score based on how quickly activity resumes after tab becomes visible
43
+ * Humans need time to refocus (100-500ms minimum)
44
+ * Bots often resume instantly (< 50ms)
45
+ */
46
+ private scoreResumeDelays;
47
+ /**
48
+ * Score based on focus-to-keypress timing
49
+ * Humans focus inputs before typing (natural delay 100-500ms)
50
+ * Bots often type without focusing or with instant delay
51
+ */
52
+ private scoreFocusTyping;
53
+ getDebugInfo(): {
54
+ eventCount: number;
55
+ actionsWhileHidden: number;
56
+ visibilityChanges: number;
57
+ focusChanges: number;
58
+ resumeDelays: number[];
59
+ focusTypingPairs: {
60
+ delay: number;
61
+ element: string;
62
+ }[];
63
+ };
64
+ }