@axeptio/behavior-detection 1.0.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 (76) hide show
  1. package/README.md +828 -0
  2. package/dist/behavior-detection.esm.min.js +2 -0
  3. package/dist/behavior-detection.esm.min.js.map +7 -0
  4. package/dist/behavior-detection.min.js +2 -0
  5. package/dist/behavior-detection.min.js.map +7 -0
  6. package/dist/behavior-detector.d.ts +102 -0
  7. package/dist/browser.d.ts +33 -0
  8. package/dist/cjs/behavior-detector.d.ts +102 -0
  9. package/dist/cjs/behavior-detector.js +315 -0
  10. package/dist/cjs/browser.d.ts +33 -0
  11. package/dist/cjs/browser.js +226 -0
  12. package/dist/cjs/index.d.ts +38 -0
  13. package/dist/cjs/index.js +55 -0
  14. package/dist/cjs/math-utils.d.ts +84 -0
  15. package/dist/cjs/math-utils.js +141 -0
  16. package/dist/cjs/strategies/click.d.ts +39 -0
  17. package/dist/cjs/strategies/click.js +173 -0
  18. package/dist/cjs/strategies/environment.d.ts +52 -0
  19. package/dist/cjs/strategies/environment.js +148 -0
  20. package/dist/cjs/strategies/index.d.ts +18 -0
  21. package/dist/cjs/strategies/index.js +36 -0
  22. package/dist/cjs/strategies/keyboard.d.ts +43 -0
  23. package/dist/cjs/strategies/keyboard.js +233 -0
  24. package/dist/cjs/strategies/mouse.d.ts +39 -0
  25. package/dist/cjs/strategies/mouse.js +159 -0
  26. package/dist/cjs/strategies/resize.d.ts +21 -0
  27. package/dist/cjs/strategies/resize.js +97 -0
  28. package/dist/cjs/strategies/scroll.d.ts +37 -0
  29. package/dist/cjs/strategies/scroll.js +149 -0
  30. package/dist/cjs/strategies/tap.d.ts +38 -0
  31. package/dist/cjs/strategies/tap.js +214 -0
  32. package/dist/cjs/strategy.d.ts +107 -0
  33. package/dist/cjs/strategy.js +33 -0
  34. package/dist/cjs/types.d.ts +168 -0
  35. package/dist/cjs/types.js +26 -0
  36. package/dist/esm/behavior-detector.d.ts +102 -0
  37. package/dist/esm/behavior-detector.js +311 -0
  38. package/dist/esm/browser.d.ts +33 -0
  39. package/dist/esm/browser.js +224 -0
  40. package/dist/esm/index.d.ts +38 -0
  41. package/dist/esm/index.js +36 -0
  42. package/dist/esm/math-utils.d.ts +84 -0
  43. package/dist/esm/math-utils.js +127 -0
  44. package/dist/esm/strategies/click.d.ts +39 -0
  45. package/dist/esm/strategies/click.js +169 -0
  46. package/dist/esm/strategies/environment.d.ts +52 -0
  47. package/dist/esm/strategies/environment.js +144 -0
  48. package/dist/esm/strategies/index.d.ts +18 -0
  49. package/dist/esm/strategies/index.js +19 -0
  50. package/dist/esm/strategies/keyboard.d.ts +43 -0
  51. package/dist/esm/strategies/keyboard.js +229 -0
  52. package/dist/esm/strategies/mouse.d.ts +39 -0
  53. package/dist/esm/strategies/mouse.js +155 -0
  54. package/dist/esm/strategies/resize.d.ts +21 -0
  55. package/dist/esm/strategies/resize.js +93 -0
  56. package/dist/esm/strategies/scroll.d.ts +37 -0
  57. package/dist/esm/strategies/scroll.js +145 -0
  58. package/dist/esm/strategies/tap.d.ts +38 -0
  59. package/dist/esm/strategies/tap.js +210 -0
  60. package/dist/esm/strategy.d.ts +107 -0
  61. package/dist/esm/strategy.js +29 -0
  62. package/dist/esm/types.d.ts +168 -0
  63. package/dist/esm/types.js +23 -0
  64. package/dist/index.d.ts +38 -0
  65. package/dist/math-utils.d.ts +84 -0
  66. package/dist/strategies/click.d.ts +39 -0
  67. package/dist/strategies/environment.d.ts +52 -0
  68. package/dist/strategies/index.d.ts +18 -0
  69. package/dist/strategies/keyboard.d.ts +43 -0
  70. package/dist/strategies/mouse.d.ts +39 -0
  71. package/dist/strategies/resize.d.ts +21 -0
  72. package/dist/strategies/scroll.d.ts +37 -0
  73. package/dist/strategies/tap.d.ts +38 -0
  74. package/dist/strategy.d.ts +107 -0
  75. package/dist/types.d.ts +168 -0
  76. package/package.json +60 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Tap Behavior Detection Strategy (Mobile)
3
+ * Monitors touch interactions on specific elements
4
+ * Analyzes tap duration, precision, movement, and timing patterns
5
+ */
6
+ import { BaseStrategy } from '../strategy';
7
+ import { analyzeIntervals, scoreCoefficientOfVariation, gaussian } from '../math-utils';
8
+ export class TapStrategy extends BaseStrategy {
9
+ constructor(options) {
10
+ super();
11
+ this.name = 'tap';
12
+ this.defaultWeight = 0.35;
13
+ this.events = [];
14
+ this.targetSelectors = ['button', 'a', 'input[type="submit"]', '[role="button"]'];
15
+ this.touchStartListeners = new Map();
16
+ this.touchEndListeners = new Map();
17
+ this.activeTouches = new Map();
18
+ this.isActive = false;
19
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
20
+ this.targetSelectors = options.targetSelectors;
21
+ }
22
+ }
23
+ start() {
24
+ if (this.isActive)
25
+ return;
26
+ this.isActive = true;
27
+ this.attachTouchListeners();
28
+ }
29
+ addTarget(selector) {
30
+ if (!this.targetSelectors.includes(selector)) {
31
+ this.targetSelectors.push(selector);
32
+ if (this.isActive) {
33
+ this.attachTouchListenersForSelector(selector);
34
+ }
35
+ }
36
+ }
37
+ attachTouchListeners() {
38
+ this.targetSelectors.forEach(selector => {
39
+ this.attachTouchListenersForSelector(selector);
40
+ });
41
+ }
42
+ attachTouchListenersForSelector(selector) {
43
+ const elements = document.querySelectorAll(selector);
44
+ elements.forEach(element => {
45
+ if (this.touchStartListeners.has(element))
46
+ return;
47
+ const startListener = (e) => {
48
+ const touchEvent = e;
49
+ // Only track first touch (ignore multi-touch)
50
+ if (touchEvent.touches.length !== 1)
51
+ return;
52
+ const touch = touchEvent.touches[0];
53
+ this.activeTouches.set(touch.identifier, {
54
+ x: touch.clientX,
55
+ y: touch.clientY,
56
+ timestamp: Date.now(),
57
+ element,
58
+ });
59
+ };
60
+ const endListener = (e) => {
61
+ const touchEvent = e;
62
+ if (touchEvent.changedTouches.length !== 1)
63
+ return;
64
+ const touch = touchEvent.changedTouches[0];
65
+ const startData = this.activeTouches.get(touch.identifier);
66
+ if (!startData || startData.element !== element)
67
+ return;
68
+ const now = Date.now();
69
+ const duration = now - startData.timestamp;
70
+ const dx = touch.clientX - startData.x;
71
+ const dy = touch.clientY - startData.y;
72
+ const movement = Math.sqrt(dx * dx + dy * dy);
73
+ const rect = element.getBoundingClientRect();
74
+ const inViewport = (rect.top >= 0 &&
75
+ rect.left >= 0 &&
76
+ rect.bottom <= window.innerHeight &&
77
+ rect.right <= window.innerWidth);
78
+ // Analyze tap position
79
+ const tx = touch.clientX;
80
+ const ty = touch.clientY;
81
+ const overElement = (tx >= rect.left &&
82
+ tx <= rect.right &&
83
+ ty >= rect.top &&
84
+ ty <= rect.bottom);
85
+ let position;
86
+ if (!overElement) {
87
+ position = 'outside';
88
+ }
89
+ else {
90
+ const centerX = rect.left + rect.width / 2;
91
+ const centerY = rect.top + rect.height / 2;
92
+ const distanceFromCenter = Math.sqrt((tx - centerX) ** 2 + (ty - centerY) ** 2);
93
+ // Dead center = within 2px
94
+ position = distanceFromCenter < 2 ? 'dead-center' : 'over-element';
95
+ }
96
+ this.events.push({
97
+ x: tx,
98
+ y: ty,
99
+ rect,
100
+ inViewport,
101
+ position,
102
+ duration,
103
+ movement,
104
+ timestamp: now,
105
+ });
106
+ // Notify detector - taps are high-value events
107
+ this.notifyEvent(1.0);
108
+ this.activeTouches.delete(touch.identifier);
109
+ };
110
+ element.addEventListener('touchstart', startListener, { passive: true });
111
+ element.addEventListener('touchend', endListener, { passive: true });
112
+ this.touchStartListeners.set(element, startListener);
113
+ this.touchEndListeners.set(element, endListener);
114
+ });
115
+ }
116
+ stop() {
117
+ if (!this.isActive)
118
+ return;
119
+ this.isActive = false;
120
+ this.touchStartListeners.forEach((listener, element) => {
121
+ element.removeEventListener('touchstart', listener);
122
+ });
123
+ this.touchStartListeners.clear();
124
+ this.touchEndListeners.forEach((listener, element) => {
125
+ element.removeEventListener('touchend', listener);
126
+ });
127
+ this.touchEndListeners.clear();
128
+ this.activeTouches.clear();
129
+ }
130
+ reset() {
131
+ this.events = [];
132
+ this.activeTouches.clear();
133
+ }
134
+ score() {
135
+ if (this.events.length === 0)
136
+ return undefined;
137
+ let score = 0;
138
+ let factors = 0;
139
+ // 1. Position accuracy
140
+ let positionScore = 0;
141
+ for (const tap of this.events) {
142
+ switch (tap.position) {
143
+ case 'outside':
144
+ positionScore += 0.0; // Bot
145
+ break;
146
+ case 'dead-center':
147
+ positionScore += 0.5; // Suspicious
148
+ break;
149
+ case 'over-element':
150
+ positionScore += 1.0; // Human
151
+ break;
152
+ }
153
+ }
154
+ score += positionScore / this.events.length;
155
+ factors++;
156
+ // 2. Tap duration variation - Humans have natural variation (50-150ms typical)
157
+ const durations = this.events.map(e => e.duration);
158
+ if (durations.length >= 2) {
159
+ const durationAnalysis = analyzeIntervals(durations);
160
+ if (durationAnalysis) {
161
+ const { statistics, allIdentical } = durationAnalysis;
162
+ // All identical = bot
163
+ if (allIdentical) {
164
+ return 0.05;
165
+ }
166
+ // Human tap durations have moderate CV (~0.3-0.5)
167
+ score += scoreCoefficientOfVariation(statistics.cv);
168
+ factors++;
169
+ // Ideal duration: 70-120ms
170
+ score += gaussian(statistics.mean, 95, 40);
171
+ factors++;
172
+ }
173
+ }
174
+ // 3. Tap movement - Real fingers move slightly (1-5px), bots are often 0px
175
+ const movements = this.events.map(e => e.movement);
176
+ if (movements.length > 0) {
177
+ const avgMovement = movements.reduce((a, b) => a + b, 0) / movements.length;
178
+ // Some movement is natural (1-5px), too much is a swipe, zero is suspicious
179
+ score += gaussian(avgMovement, 2, 3);
180
+ factors++;
181
+ }
182
+ // 4. Tap interval timing - Time between taps should vary naturally
183
+ if (this.events.length >= 3) {
184
+ const intervals = [];
185
+ for (let i = 1; i < this.events.length; i++) {
186
+ intervals.push(this.events[i].timestamp - this.events[i - 1].timestamp);
187
+ }
188
+ const intervalAnalysis = analyzeIntervals(intervals);
189
+ if (intervalAnalysis && !intervalAnalysis.allIdentical) {
190
+ score += scoreCoefficientOfVariation(intervalAnalysis.statistics.cv);
191
+ factors++;
192
+ }
193
+ }
194
+ return factors > 0 ? score / factors : undefined;
195
+ }
196
+ getDebugInfo() {
197
+ return {
198
+ eventCount: this.events.length,
199
+ positions: {
200
+ outside: this.events.filter(e => e.position === 'outside').length,
201
+ deadCenter: this.events.filter(e => e.position === 'dead-center').length,
202
+ overElement: this.events.filter(e => e.position === 'over-element').length,
203
+ },
204
+ durations: this.events.map(e => e.duration),
205
+ movements: this.events.map(e => e.movement),
206
+ inViewport: this.events.filter(e => e.inViewport).length,
207
+ trackedElements: this.touchStartListeners.size,
208
+ };
209
+ }
210
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Detection Strategy Interface
3
+ * Each strategy is a fully autonomous module responsible for:
4
+ * - Registering its own event listeners
5
+ * - Managing its own state and events
6
+ * - Responding to lifecycle events (start/stop)
7
+ * - Optionally receiving tick updates for polling-based detection
8
+ */
9
+ /**
10
+ * Time series data point
11
+ * Used by strategies that track values over time
12
+ */
13
+ export interface TimeSeriesPoint {
14
+ value: number;
15
+ timestamp: number;
16
+ }
17
+ /**
18
+ * Event notification from strategy to detector
19
+ */
20
+ export interface StrategyEvent {
21
+ strategy: string;
22
+ weight: number;
23
+ timestamp: number;
24
+ }
25
+ /**
26
+ * Base class for detection strategies
27
+ * Handles event callback pattern to avoid repetition
28
+ */
29
+ export declare abstract class BaseStrategy implements DetectionStrategy {
30
+ abstract readonly name: string;
31
+ abstract readonly defaultWeight: number;
32
+ protected eventCallback: ((event: StrategyEvent) => void) | null;
33
+ setEventCallback(callback: (event: StrategyEvent) => void): void;
34
+ protected notifyEvent(weight: number): void;
35
+ abstract start(): void;
36
+ abstract stop(): void;
37
+ abstract reset(): void;
38
+ abstract score(): number | undefined;
39
+ onTick?(timestamp: number): void;
40
+ getDebugInfo?(): any;
41
+ }
42
+ export interface DetectionStrategy {
43
+ /**
44
+ * Unique identifier for this strategy
45
+ */
46
+ readonly name: string;
47
+ /**
48
+ * Default weight for this strategy in overall score calculation
49
+ */
50
+ readonly defaultWeight: number;
51
+ /**
52
+ * Start detection - register event listeners
53
+ */
54
+ start(): void;
55
+ /**
56
+ * Stop detection - remove event listeners, cleanup
57
+ */
58
+ stop(): void;
59
+ /**
60
+ * Reset collected data
61
+ */
62
+ reset(): void;
63
+ /**
64
+ * Calculate score based on collected data
65
+ * @returns Score 0-1, or undefined if insufficient data
66
+ */
67
+ score(): number | undefined;
68
+ /**
69
+ * Optional: Called on regular interval for polling-based detection
70
+ * Use this to sample document/window/navigator state
71
+ * @param timestamp - Current timestamp
72
+ */
73
+ onTick?(timestamp: number): void;
74
+ /**
75
+ * Optional: Get debug information about collected data
76
+ */
77
+ getDebugInfo?(): any;
78
+ /**
79
+ * Optional: Set callback for when events are received
80
+ * Used to track confidence - detector will be notified when strategy receives data
81
+ * @param callback - Function to call when event is received
82
+ */
83
+ setEventCallback?(callback: (event: StrategyEvent) => void): void;
84
+ }
85
+ export interface StrategyConfig {
86
+ strategy: DetectionStrategy;
87
+ weight: number;
88
+ enabled: boolean;
89
+ }
90
+ export interface TickOptions {
91
+ /**
92
+ * Tick interval in milliseconds
93
+ * Default: 1000 (1 second)
94
+ */
95
+ interval?: number;
96
+ /**
97
+ * Whether to start ticking immediately
98
+ * Default: false (start with detector.start())
99
+ */
100
+ autoStart?: boolean;
101
+ /**
102
+ * Automatically pause detection when the tab is hidden
103
+ * Uses the Page Visibility API to stop strategies and tick when document.hidden is true
104
+ * Default: true (recommended for better performance and battery life)
105
+ */
106
+ pauseOnHidden?: boolean;
107
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Detection Strategy Interface
3
+ * Each strategy is a fully autonomous module responsible for:
4
+ * - Registering its own event listeners
5
+ * - Managing its own state and events
6
+ * - Responding to lifecycle events (start/stop)
7
+ * - Optionally receiving tick updates for polling-based detection
8
+ */
9
+ /**
10
+ * Base class for detection strategies
11
+ * Handles event callback pattern to avoid repetition
12
+ */
13
+ export class BaseStrategy {
14
+ constructor() {
15
+ this.eventCallback = null;
16
+ }
17
+ setEventCallback(callback) {
18
+ this.eventCallback = callback;
19
+ }
20
+ notifyEvent(weight) {
21
+ if (this.eventCallback) {
22
+ this.eventCallback({
23
+ strategy: this.name,
24
+ weight,
25
+ timestamp: Date.now()
26
+ });
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,168 @@
1
+ export type EventType = 'tab-visibility' | 'scroll' | 'resize' | 'mouse-move' | 'click' | 'keypress' | 'environment';
2
+ export interface BaseEvent {
3
+ type: EventType;
4
+ timestamp: number;
5
+ }
6
+ export interface TabVisibilityEvent extends BaseEvent {
7
+ type: 'tab-visibility';
8
+ hidden: boolean;
9
+ }
10
+ export interface ScrollEvent extends BaseEvent {
11
+ type: 'scroll';
12
+ x: number;
13
+ y: number;
14
+ elementId?: string;
15
+ deltaY?: number;
16
+ deltaTime?: number;
17
+ velocity?: number;
18
+ isProgrammatic?: boolean;
19
+ }
20
+ export interface ResizeEvent extends BaseEvent {
21
+ type: 'resize';
22
+ width: number;
23
+ height: number;
24
+ mouseX?: number;
25
+ mouseY?: number;
26
+ mouseNearEdge?: boolean;
27
+ screenWidth: number;
28
+ screenHeight: number;
29
+ screenAvailWidth: number;
30
+ screenAvailHeight: number;
31
+ devicePixelRatio: number;
32
+ isFullscreen?: boolean;
33
+ }
34
+ export interface MouseMoveEvent extends BaseEvent {
35
+ type: 'mouse-move';
36
+ x: number;
37
+ y: number;
38
+ velocityX?: number;
39
+ velocityY?: number;
40
+ directionChange?: boolean;
41
+ }
42
+ export interface ClickEvent extends BaseEvent {
43
+ type: 'click';
44
+ x: number;
45
+ y: number;
46
+ targetX: number;
47
+ targetY: number;
48
+ targetWidth: number;
49
+ targetHeight: number;
50
+ distanceFromTarget: number;
51
+ mousePositionBeforeClick?: {
52
+ x: number;
53
+ y: number;
54
+ timestamp: number;
55
+ };
56
+ timeSinceLastMouseMove?: number;
57
+ targetInViewport: boolean;
58
+ targetVisibleArea: number;
59
+ }
60
+ export interface KeypressEvent extends BaseEvent {
61
+ type: 'keypress';
62
+ key: string;
63
+ timeSinceLastKey?: number;
64
+ }
65
+ export interface EnvironmentEvent extends BaseEvent {
66
+ type: 'environment';
67
+ screenWidth: number;
68
+ screenHeight: number;
69
+ screenAvailWidth: number;
70
+ screenAvailHeight: number;
71
+ windowWidth: number;
72
+ windowHeight: number;
73
+ devicePixelRatio: number;
74
+ colorDepth: number;
75
+ pixelDepth: number;
76
+ userAgent: string;
77
+ platform: string;
78
+ language: string;
79
+ languages: string[];
80
+ hardwareConcurrency?: number;
81
+ maxTouchPoints: number;
82
+ vendor: string;
83
+ hasWebGL: boolean;
84
+ hasWebRTC: boolean;
85
+ hasNotifications: boolean;
86
+ hasGeolocation: boolean;
87
+ hasIndexedDB: boolean;
88
+ hasLocalStorage: boolean;
89
+ hasSessionStorage: boolean;
90
+ plugins: number;
91
+ mimeTypes: number;
92
+ suspiciousRatio?: boolean;
93
+ suspiciousDimensions?: boolean;
94
+ featureInconsistency?: boolean;
95
+ }
96
+ export type TrackedEvent = TabVisibilityEvent | ScrollEvent | ResizeEvent | MouseMoveEvent | ClickEvent | KeypressEvent | EnvironmentEvent;
97
+ export interface EventStorage {
98
+ 'tab-visibility': TabVisibilityEvent[];
99
+ 'scroll': ScrollEvent[];
100
+ 'resize': ResizeEvent[];
101
+ 'mouse-move': MouseMoveEvent[];
102
+ 'click': ClickEvent[];
103
+ 'keypress': KeypressEvent[];
104
+ 'environment': EnvironmentEvent[];
105
+ }
106
+ export interface ScoreBreakdown {
107
+ overall: number;
108
+ factors: {
109
+ mouseMovement?: number;
110
+ clickAccuracy?: number;
111
+ scrollBehavior?: number;
112
+ keyboardTiming?: number;
113
+ tabActivity?: number;
114
+ resizeBehavior?: number;
115
+ environmentFingerprint?: number;
116
+ };
117
+ weights: {
118
+ mouseMovement: number;
119
+ clickAccuracy: number;
120
+ scrollBehavior: number;
121
+ keyboardTiming: number;
122
+ tabActivity: number;
123
+ resizeBehavior: number;
124
+ environmentFingerprint: number;
125
+ };
126
+ }
127
+ export interface ScoreOptions {
128
+ breakdown?: boolean;
129
+ auditTrail?: boolean;
130
+ }
131
+ export interface ScoreResult {
132
+ score: number;
133
+ breakdown?: ScoreBreakdown;
134
+ auditTrail?: TrackedEvent[];
135
+ }
136
+ export type ScoringFunction = (events: TrackedEvent[]) => number | undefined;
137
+ export interface BehaviorSettings {
138
+ sampleRates?: {
139
+ mouseMove?: number;
140
+ scroll?: number;
141
+ keypress?: number;
142
+ };
143
+ rollingWindows?: {
144
+ mouseMove?: number;
145
+ scroll?: number;
146
+ };
147
+ weights?: {
148
+ mouseMovement?: number;
149
+ clickAccuracy?: number;
150
+ scrollBehavior?: number;
151
+ keyboardTiming?: number;
152
+ tabActivity?: number;
153
+ resizeBehavior?: number;
154
+ environmentFingerprint?: number;
155
+ };
156
+ customScorers?: {
157
+ mouseMovement?: ScoringFunction;
158
+ clickAccuracy?: ScoringFunction;
159
+ scrollBehavior?: ScoringFunction;
160
+ keyboardTiming?: ScoringFunction;
161
+ tabActivity?: ScoringFunction;
162
+ resizeBehavior?: ScoringFunction;
163
+ environmentFingerprint?: ScoringFunction;
164
+ };
165
+ clickMouseHistoryWindow?: number;
166
+ useWebWorker?: boolean;
167
+ }
168
+ export declare const DEFAULT_SETTINGS: Required<BehaviorSettings>;
@@ -0,0 +1,23 @@
1
+ export const DEFAULT_SETTINGS = {
2
+ sampleRates: {
3
+ mouseMove: 0.1, // Track 10% of mouse moves
4
+ scroll: 1.0, // Track ALL scrolls - critical for detection
5
+ keypress: 1.0, // Track all keypresses
6
+ },
7
+ rollingWindows: {
8
+ mouseMove: 30000,
9
+ scroll: 30000,
10
+ },
11
+ weights: {
12
+ mouseMovement: 0.30, // Increased - continuous behavioral signal
13
+ clickAccuracy: 0.30, // Increased - critical behavioral signal
14
+ scrollBehavior: 0.15, // Unchanged
15
+ keyboardTiming: 0.10, // Slightly reduced
16
+ tabActivity: 0.05, // Unchanged
17
+ resizeBehavior: 0.02, // Reduced - less reliable
18
+ environmentFingerprint: 0.08, // Reduced - static signal, one-time check
19
+ },
20
+ customScorers: {},
21
+ clickMouseHistoryWindow: 1000,
22
+ useWebWorker: false, // Will implement Web Worker support in phase 2
23
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @axeptio/behavior-detection
3
+ *
4
+ * Lightweight behavior detection library to assess human likelihood of user sessions
5
+ *
6
+ * @example Settings-based (all strategies included)
7
+ * ```ts
8
+ * import { BehaviorDetector } from '@axeptio/behavior-detection';
9
+ *
10
+ * const detector = new BehaviorDetector({
11
+ * weights: {
12
+ * mouseMovement: 0.3,
13
+ * clickAccuracy: 0.3,
14
+ * },
15
+ * });
16
+ *
17
+ * detector.start();
18
+ * const result = await detector.score({ breakdown: true });
19
+ * ```
20
+ *
21
+ * @example Strategy-based (tree-shakeable, import only what you need)
22
+ * ```ts
23
+ * import { BehaviorDetector, Mouse, Click, Keyboard } from '@axeptio/behavior-detection';
24
+ *
25
+ * const detector = new BehaviorDetector()
26
+ * .addStrategy(new Mouse())
27
+ * .addStrategy(new Click())
28
+ * .addStrategy(new Keyboard());
29
+ *
30
+ * detector.start();
31
+ * const result = await detector.score();
32
+ * ```
33
+ */
34
+ export { BehaviorDetector } from './behavior-detector';
35
+ export type { DetectionStrategy, StrategyConfig } from './strategy';
36
+ export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, } from './strategies/index.js';
37
+ export type { BehaviorSettings, ScoreOptions, ScoreResult, ScoreBreakdown, TrackedEvent, EventType, ScoringFunction, } from './types';
38
+ export { DEFAULT_SETTINGS } from './types';
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Mathematical utility functions for continuous scoring
3
+ * Replaces magic number if-else chains with smooth mathematical functions
4
+ */
5
+ /**
6
+ * Sigmoid function - Maps any value to [0, 1] with smooth S-curve
7
+ * @param x - Input value
8
+ * @param midpoint - Value that maps to 0.5
9
+ * @param steepness - How steep the curve is (higher = steeper)
10
+ */
11
+ export declare function sigmoid(x: number, midpoint?: number, steepness?: number): number;
12
+ /**
13
+ * Inverse sigmoid - High values map to low scores
14
+ */
15
+ export declare function inverseSigmoid(x: number, midpoint?: number, steepness?: number): number;
16
+ /**
17
+ * Gaussian (bell curve) - Peak at ideal value, falls off on both sides
18
+ * @param x - Input value
19
+ * @param ideal - Optimal value (peak of curve)
20
+ * @param width - How wide the acceptable range is
21
+ */
22
+ export declare function gaussian(x: number, ideal?: number, width?: number): number;
23
+ /**
24
+ * Exponential decay - High values get penalized exponentially
25
+ */
26
+ export declare function exponentialDecay(x: number, decayRate?: number): number;
27
+ /**
28
+ * Normalize value to [0, 1] range
29
+ */
30
+ export declare function normalize(value: number, min?: number, max?: number): number;
31
+ /**
32
+ * Clamp value to [0, 1]
33
+ */
34
+ export declare function clamp01(value: number): number;
35
+ /**
36
+ * Statistical metrics for an array of numbers
37
+ */
38
+ export interface Statistics {
39
+ mean: number;
40
+ variance: number;
41
+ stdDev: number;
42
+ cv: number;
43
+ }
44
+ /**
45
+ * Calculate statistical metrics for an array of numbers
46
+ * @param values - Array of numerical values
47
+ * @returns Statistical metrics (mean, variance, stdDev, cv)
48
+ */
49
+ export declare function calculateStatistics(values: number[]): Statistics;
50
+ /**
51
+ * Analysis result for interval patterns
52
+ */
53
+ export interface IntervalAnalysis {
54
+ statistics: Statistics;
55
+ uniqueCount: number;
56
+ allIdentical: boolean;
57
+ }
58
+ /**
59
+ * Analyze timing intervals for bot-like patterns
60
+ * Returns raw analysis data without scoring - caller decides heuristics
61
+ *
62
+ * @param intervals - Array of time intervals (ms)
63
+ * @returns Analysis of interval patterns including stats and uniqueness
64
+ */
65
+ export declare function analyzeIntervals(intervals: number[]): IntervalAnalysis | undefined;
66
+ /**
67
+ * Score coefficient of variation
68
+ * Maps CV to human-likeness score using Gaussian
69
+ * Ideal CV is around 0.4-0.5 (moderate variation)
70
+ */
71
+ export declare function scoreCoefficientOfVariation(cv: number): number;
72
+ /**
73
+ * Score entropy - Higher is better (more random = more human)
74
+ * Uses sigmoid to smoothly map entropy to score
75
+ */
76
+ export declare function scoreEntropy(normalizedEntropy: number): number;
77
+ /**
78
+ * Score autocorrelation - Lower is better (less periodic = more human)
79
+ */
80
+ export declare function scoreAutocorrelation(autocorr: number): number;
81
+ /**
82
+ * Score jerk - Lower is better (smoother = more human)
83
+ */
84
+ export declare function scoreJerk(avgJerk: number): number;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Click Behavior Detection Strategy
3
+ * Monitors specific elements for click positioning patterns
4
+ */
5
+ import { BaseStrategy } from '../strategy';
6
+ export declare class ClickStrategy extends BaseStrategy {
7
+ readonly name = "click";
8
+ readonly defaultWeight = 0.3;
9
+ private events;
10
+ private targetSelectors;
11
+ private lastMousePosition;
12
+ private mouseListener;
13
+ private clickListeners;
14
+ private isActive;
15
+ constructor(options?: {
16
+ targetSelectors?: string[];
17
+ });
18
+ start(): void;
19
+ /**
20
+ * Add a new target selector at runtime
21
+ */
22
+ addTarget(selector: string): void;
23
+ private attachClickListeners;
24
+ private attachClickListenersForSelector;
25
+ stop(): void;
26
+ reset(): void;
27
+ score(): number | undefined;
28
+ getDebugInfo(): {
29
+ eventCount: number;
30
+ positions: {
31
+ noMouseData: number;
32
+ outside: number;
33
+ deadCenter: number;
34
+ overElement: number;
35
+ };
36
+ inViewport: number;
37
+ trackedElements: number;
38
+ };
39
+ }