@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.
- package/README.md +828 -0
- package/dist/behavior-detection.esm.min.js +2 -0
- package/dist/behavior-detection.esm.min.js.map +7 -0
- package/dist/behavior-detection.min.js +2 -0
- package/dist/behavior-detection.min.js.map +7 -0
- package/dist/behavior-detector.d.ts +102 -0
- package/dist/browser.d.ts +33 -0
- package/dist/cjs/behavior-detector.d.ts +102 -0
- package/dist/cjs/behavior-detector.js +315 -0
- package/dist/cjs/browser.d.ts +33 -0
- package/dist/cjs/browser.js +226 -0
- package/dist/cjs/index.d.ts +38 -0
- package/dist/cjs/index.js +55 -0
- package/dist/cjs/math-utils.d.ts +84 -0
- package/dist/cjs/math-utils.js +141 -0
- package/dist/cjs/strategies/click.d.ts +39 -0
- package/dist/cjs/strategies/click.js +173 -0
- package/dist/cjs/strategies/environment.d.ts +52 -0
- package/dist/cjs/strategies/environment.js +148 -0
- package/dist/cjs/strategies/index.d.ts +18 -0
- package/dist/cjs/strategies/index.js +36 -0
- package/dist/cjs/strategies/keyboard.d.ts +43 -0
- package/dist/cjs/strategies/keyboard.js +233 -0
- package/dist/cjs/strategies/mouse.d.ts +39 -0
- package/dist/cjs/strategies/mouse.js +159 -0
- package/dist/cjs/strategies/resize.d.ts +21 -0
- package/dist/cjs/strategies/resize.js +97 -0
- package/dist/cjs/strategies/scroll.d.ts +37 -0
- package/dist/cjs/strategies/scroll.js +149 -0
- package/dist/cjs/strategies/tap.d.ts +38 -0
- package/dist/cjs/strategies/tap.js +214 -0
- package/dist/cjs/strategy.d.ts +107 -0
- package/dist/cjs/strategy.js +33 -0
- package/dist/cjs/types.d.ts +168 -0
- package/dist/cjs/types.js +26 -0
- package/dist/esm/behavior-detector.d.ts +102 -0
- package/dist/esm/behavior-detector.js +311 -0
- package/dist/esm/browser.d.ts +33 -0
- package/dist/esm/browser.js +224 -0
- package/dist/esm/index.d.ts +38 -0
- package/dist/esm/index.js +36 -0
- package/dist/esm/math-utils.d.ts +84 -0
- package/dist/esm/math-utils.js +127 -0
- package/dist/esm/strategies/click.d.ts +39 -0
- package/dist/esm/strategies/click.js +169 -0
- package/dist/esm/strategies/environment.d.ts +52 -0
- package/dist/esm/strategies/environment.js +144 -0
- package/dist/esm/strategies/index.d.ts +18 -0
- package/dist/esm/strategies/index.js +19 -0
- package/dist/esm/strategies/keyboard.d.ts +43 -0
- package/dist/esm/strategies/keyboard.js +229 -0
- package/dist/esm/strategies/mouse.d.ts +39 -0
- package/dist/esm/strategies/mouse.js +155 -0
- package/dist/esm/strategies/resize.d.ts +21 -0
- package/dist/esm/strategies/resize.js +93 -0
- package/dist/esm/strategies/scroll.d.ts +37 -0
- package/dist/esm/strategies/scroll.js +145 -0
- package/dist/esm/strategies/tap.d.ts +38 -0
- package/dist/esm/strategies/tap.js +210 -0
- package/dist/esm/strategy.d.ts +107 -0
- package/dist/esm/strategy.js +29 -0
- package/dist/esm/types.d.ts +168 -0
- package/dist/esm/types.js +23 -0
- package/dist/index.d.ts +38 -0
- package/dist/math-utils.d.ts +84 -0
- package/dist/strategies/click.d.ts +39 -0
- package/dist/strategies/environment.d.ts +52 -0
- package/dist/strategies/index.d.ts +18 -0
- package/dist/strategies/keyboard.d.ts +43 -0
- package/dist/strategies/mouse.d.ts +39 -0
- package/dist/strategies/resize.d.ts +21 -0
- package/dist/strategies/scroll.d.ts +37 -0
- package/dist/strategies/tap.d.ts +38 -0
- package/dist/strategy.d.ts +107 -0
- package/dist/types.d.ts +168 -0
- package/package.json +60 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Build - Auto-initializing standalone bundle for CDN usage
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <script src="https://cdn.../behavior-detector.js"></script>
|
|
6
|
+
* <script>
|
|
7
|
+
* window.bdSettings = {
|
|
8
|
+
* strategies: {
|
|
9
|
+
* mouse: { weight: 0.3 },
|
|
10
|
+
* scroll: { weight: 0.15 },
|
|
11
|
+
* click: { weight: 0.3 },
|
|
12
|
+
* keyboard: { weight: 0.1 },
|
|
13
|
+
* environment: { weight: 0.08 },
|
|
14
|
+
* resize: { weight: 0.02 }
|
|
15
|
+
* },
|
|
16
|
+
* autoStart: true,
|
|
17
|
+
* checkMs: 2000,
|
|
18
|
+
* pauseOnHidden: true, // Auto-pause when tab hidden (default: true)
|
|
19
|
+
* onScore: (result) => {
|
|
20
|
+
* console.log('Human likelihood:', result.score);
|
|
21
|
+
* if (result.score < 0.3) {
|
|
22
|
+
* // Likely bot
|
|
23
|
+
* }
|
|
24
|
+
* },
|
|
25
|
+
* ifBot: (result) => {
|
|
26
|
+
* // Called when score < threshold
|
|
27
|
+
* console.warn('Bot detected:', result);
|
|
28
|
+
* },
|
|
29
|
+
* botThreshold: 0.3
|
|
30
|
+
* }
|
|
31
|
+
* </script>
|
|
32
|
+
*/
|
|
33
|
+
import { BehaviorDetector } from './behavior-detector';
|
|
34
|
+
import { MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, } from './strategies/index.js';
|
|
35
|
+
/**
|
|
36
|
+
* Detect if the current device is mobile
|
|
37
|
+
*/
|
|
38
|
+
function isMobileDevice() {
|
|
39
|
+
const hasTouchScreen = navigator.maxTouchPoints > 0 || 'ontouchstart' in window;
|
|
40
|
+
const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
41
|
+
const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
|
|
42
|
+
return (hasTouchScreen && smallScreen) || mobileUA;
|
|
43
|
+
}
|
|
44
|
+
class BehaviorDetectionAPI {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.detector = null;
|
|
47
|
+
this.checkInterval = null;
|
|
48
|
+
this.settings = null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Initialize the detector with settings
|
|
52
|
+
*/
|
|
53
|
+
init(settings) {
|
|
54
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
55
|
+
if (this.detector) {
|
|
56
|
+
this.stop();
|
|
57
|
+
}
|
|
58
|
+
this.settings = settings;
|
|
59
|
+
this.detector = new BehaviorDetector({
|
|
60
|
+
pauseOnHidden: settings.pauseOnHidden !== undefined ? settings.pauseOnHidden : true
|
|
61
|
+
});
|
|
62
|
+
// Detect if mobile to adjust strategy selection and weights
|
|
63
|
+
const isMobile = isMobileDevice();
|
|
64
|
+
const strategies = settings.strategies || {};
|
|
65
|
+
// Mobile-adjusted default weights
|
|
66
|
+
const defaultWeights = isMobile ? {
|
|
67
|
+
mouse: 0, // Disabled on mobile
|
|
68
|
+
scroll: 0.35, // Increased (more reliable on mobile)
|
|
69
|
+
click: 0, // Disabled on mobile (use tap instead)
|
|
70
|
+
tap: 0.35, // Mobile tap detection
|
|
71
|
+
keyboard: 0.15,
|
|
72
|
+
environment: 0.10,
|
|
73
|
+
resize: 0.05, // Orientation changes
|
|
74
|
+
} : {
|
|
75
|
+
mouse: 0.30,
|
|
76
|
+
scroll: 0.15,
|
|
77
|
+
click: 0.30,
|
|
78
|
+
tap: 0, // Disabled on desktop
|
|
79
|
+
keyboard: 0.10,
|
|
80
|
+
environment: 0.08,
|
|
81
|
+
resize: 0.02,
|
|
82
|
+
};
|
|
83
|
+
// Mouse - Desktop only
|
|
84
|
+
if (!isMobile && ((_a = strategies.mouse) === null || _a === void 0 ? void 0 : _a.enabled) !== false) {
|
|
85
|
+
const strategy = new MouseStrategy();
|
|
86
|
+
const weight = (_c = (_b = strategies.mouse) === null || _b === void 0 ? void 0 : _b.weight) !== null && _c !== void 0 ? _c : defaultWeights.mouse;
|
|
87
|
+
this.detector.addStrategy(strategy, weight);
|
|
88
|
+
}
|
|
89
|
+
// Scroll - Universal
|
|
90
|
+
if (((_d = strategies.scroll) === null || _d === void 0 ? void 0 : _d.enabled) !== false) {
|
|
91
|
+
const strategy = new ScrollStrategy();
|
|
92
|
+
const weight = (_f = (_e = strategies.scroll) === null || _e === void 0 ? void 0 : _e.weight) !== null && _f !== void 0 ? _f : defaultWeights.scroll;
|
|
93
|
+
this.detector.addStrategy(strategy, weight);
|
|
94
|
+
}
|
|
95
|
+
// Click - Desktop only
|
|
96
|
+
if (!isMobile && ((_g = strategies.click) === null || _g === void 0 ? void 0 : _g.enabled) !== false) {
|
|
97
|
+
const strategy = new ClickStrategy();
|
|
98
|
+
const weight = (_j = (_h = strategies.click) === null || _h === void 0 ? void 0 : _h.weight) !== null && _j !== void 0 ? _j : defaultWeights.click;
|
|
99
|
+
this.detector.addStrategy(strategy, weight);
|
|
100
|
+
}
|
|
101
|
+
// Tap - Mobile only
|
|
102
|
+
if (isMobile && ((_k = strategies.tap) === null || _k === void 0 ? void 0 : _k.enabled) !== false) {
|
|
103
|
+
const strategy = new TapStrategy();
|
|
104
|
+
const weight = (_m = (_l = strategies.tap) === null || _l === void 0 ? void 0 : _l.weight) !== null && _m !== void 0 ? _m : defaultWeights.tap;
|
|
105
|
+
this.detector.addStrategy(strategy, weight);
|
|
106
|
+
}
|
|
107
|
+
// Keyboard - Universal
|
|
108
|
+
if (((_o = strategies.keyboard) === null || _o === void 0 ? void 0 : _o.enabled) !== false) {
|
|
109
|
+
const strategy = new KeyboardStrategy();
|
|
110
|
+
const weight = (_q = (_p = strategies.keyboard) === null || _p === void 0 ? void 0 : _p.weight) !== null && _q !== void 0 ? _q : defaultWeights.keyboard;
|
|
111
|
+
this.detector.addStrategy(strategy, weight);
|
|
112
|
+
}
|
|
113
|
+
// Environment - Universal
|
|
114
|
+
if (((_r = strategies.environment) === null || _r === void 0 ? void 0 : _r.enabled) !== false) {
|
|
115
|
+
const strategy = new EnvironmentStrategy();
|
|
116
|
+
const weight = (_t = (_s = strategies.environment) === null || _s === void 0 ? void 0 : _s.weight) !== null && _t !== void 0 ? _t : defaultWeights.environment;
|
|
117
|
+
this.detector.addStrategy(strategy, weight);
|
|
118
|
+
}
|
|
119
|
+
// Resize - Universal
|
|
120
|
+
if (((_u = strategies.resize) === null || _u === void 0 ? void 0 : _u.enabled) !== false) {
|
|
121
|
+
const strategy = new ResizeStrategy();
|
|
122
|
+
const weight = (_w = (_v = strategies.resize) === null || _v === void 0 ? void 0 : _v.weight) !== null && _w !== void 0 ? _w : defaultWeights.resize;
|
|
123
|
+
this.detector.addStrategy(strategy, weight);
|
|
124
|
+
}
|
|
125
|
+
// Start if autoStart is enabled
|
|
126
|
+
if (settings.autoStart !== false) {
|
|
127
|
+
this.start();
|
|
128
|
+
}
|
|
129
|
+
// Set up periodic checking if checkMs is specified
|
|
130
|
+
if (settings.checkMs && settings.checkMs > 0) {
|
|
131
|
+
this.startPeriodicCheck(settings.checkMs);
|
|
132
|
+
}
|
|
133
|
+
// Call onReady callback
|
|
134
|
+
if (settings.onReady) {
|
|
135
|
+
settings.onReady(this);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Start detection
|
|
140
|
+
*/
|
|
141
|
+
start() {
|
|
142
|
+
if (!this.detector) {
|
|
143
|
+
console.warn('BehaviorDetector: Call init() before start()');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.detector.start();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Stop detection
|
|
150
|
+
*/
|
|
151
|
+
stop() {
|
|
152
|
+
if (this.detector) {
|
|
153
|
+
this.detector.stop();
|
|
154
|
+
}
|
|
155
|
+
if (this.checkInterval !== null) {
|
|
156
|
+
clearInterval(this.checkInterval);
|
|
157
|
+
this.checkInterval = null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get current score
|
|
162
|
+
*/
|
|
163
|
+
async score() {
|
|
164
|
+
if (!this.detector) {
|
|
165
|
+
console.warn('BehaviorDetector: Call init() before score()');
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return this.detector.score({ breakdown: true });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Start periodic checking
|
|
172
|
+
*/
|
|
173
|
+
startPeriodicCheck(intervalMs) {
|
|
174
|
+
if (this.checkInterval !== null) {
|
|
175
|
+
clearInterval(this.checkInterval);
|
|
176
|
+
}
|
|
177
|
+
this.checkInterval = window.setInterval(async () => {
|
|
178
|
+
var _a, _b;
|
|
179
|
+
const result = await this.score();
|
|
180
|
+
if (!result || !this.settings)
|
|
181
|
+
return;
|
|
182
|
+
// Call onScore callback
|
|
183
|
+
if (this.settings.onScore) {
|
|
184
|
+
this.settings.onScore(result);
|
|
185
|
+
}
|
|
186
|
+
// Check thresholds
|
|
187
|
+
const botThreshold = (_a = this.settings.botThreshold) !== null && _a !== void 0 ? _a : 0.3;
|
|
188
|
+
const humanThreshold = (_b = this.settings.humanThreshold) !== null && _b !== void 0 ? _b : 0.7;
|
|
189
|
+
if (result.score < botThreshold && this.settings.ifBot) {
|
|
190
|
+
this.settings.ifBot(result);
|
|
191
|
+
}
|
|
192
|
+
else if (result.score >= humanThreshold && this.settings.ifHuman) {
|
|
193
|
+
this.settings.ifHuman(result);
|
|
194
|
+
}
|
|
195
|
+
}, intervalMs);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get the underlying detector instance for advanced usage
|
|
199
|
+
*/
|
|
200
|
+
getDetector() {
|
|
201
|
+
return this.detector;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Create global API instance
|
|
205
|
+
const api = new BehaviorDetectionAPI();
|
|
206
|
+
// Auto-initialize if settings are already present
|
|
207
|
+
if (typeof window !== 'undefined') {
|
|
208
|
+
// Expose API globally
|
|
209
|
+
window.BehaviorDetector = api;
|
|
210
|
+
// Check for settings on load
|
|
211
|
+
const checkAndInit = () => {
|
|
212
|
+
const settings = window.bdSettings;
|
|
213
|
+
if (settings) {
|
|
214
|
+
api.init(settings);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
// Try immediate init
|
|
218
|
+
checkAndInit();
|
|
219
|
+
// Also try after DOM ready
|
|
220
|
+
if (document.readyState === 'loading') {
|
|
221
|
+
document.addEventListener('DOMContentLoaded', checkAndInit);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// No exports needed for browser IIFE build - window.BehaviorDetector is sufficient
|
|
@@ -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,36 @@
|
|
|
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 { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, } from './strategies/index.js';
|
|
36
|
+
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,127 @@
|
|
|
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 function sigmoid(x, midpoint = 0, steepness = 1) {
|
|
12
|
+
return 1 / (1 + Math.exp(-steepness * (x - midpoint)));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Inverse sigmoid - High values map to low scores
|
|
16
|
+
*/
|
|
17
|
+
export function inverseSigmoid(x, midpoint = 0, steepness = 1) {
|
|
18
|
+
return 1 - sigmoid(x, midpoint, steepness);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Gaussian (bell curve) - Peak at ideal value, falls off on both sides
|
|
22
|
+
* @param x - Input value
|
|
23
|
+
* @param ideal - Optimal value (peak of curve)
|
|
24
|
+
* @param width - How wide the acceptable range is
|
|
25
|
+
*/
|
|
26
|
+
export function gaussian(x, ideal = 0, width = 1) {
|
|
27
|
+
const exponent = -Math.pow(x - ideal, 2) / (2 * width * width);
|
|
28
|
+
return Math.exp(exponent);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Exponential decay - High values get penalized exponentially
|
|
32
|
+
*/
|
|
33
|
+
export function exponentialDecay(x, decayRate = 0.1) {
|
|
34
|
+
return Math.exp(-decayRate * x);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalize value to [0, 1] range
|
|
38
|
+
*/
|
|
39
|
+
export function normalize(value, min = 0, max = 1) {
|
|
40
|
+
if (max === min)
|
|
41
|
+
return 0.5;
|
|
42
|
+
return Math.max(0, Math.min(1, (value - min) / (max - min)));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clamp value to [0, 1]
|
|
46
|
+
*/
|
|
47
|
+
export function clamp01(value) {
|
|
48
|
+
return Math.max(0, Math.min(1, value));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Calculate statistical metrics for an array of numbers
|
|
52
|
+
* @param values - Array of numerical values
|
|
53
|
+
* @returns Statistical metrics (mean, variance, stdDev, cv)
|
|
54
|
+
*/
|
|
55
|
+
export function calculateStatistics(values) {
|
|
56
|
+
if (values.length === 0) {
|
|
57
|
+
return { mean: 0, variance: 0, stdDev: 0, cv: 0 };
|
|
58
|
+
}
|
|
59
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
60
|
+
const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
|
|
61
|
+
const stdDev = Math.sqrt(variance);
|
|
62
|
+
const cv = mean > 0 ? stdDev / mean : 0;
|
|
63
|
+
return { mean, variance, stdDev, cv };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Analyze timing intervals for bot-like patterns
|
|
67
|
+
* Returns raw analysis data without scoring - caller decides heuristics
|
|
68
|
+
*
|
|
69
|
+
* @param intervals - Array of time intervals (ms)
|
|
70
|
+
* @returns Analysis of interval patterns including stats and uniqueness
|
|
71
|
+
*/
|
|
72
|
+
export function analyzeIntervals(intervals) {
|
|
73
|
+
if (intervals.length < 2)
|
|
74
|
+
return undefined;
|
|
75
|
+
// Check for identical intervals (round to 10ms to account for floating point)
|
|
76
|
+
const uniqueCount = new Set(intervals.map(i => Math.round(i / 10))).size;
|
|
77
|
+
const allIdentical = uniqueCount === 1;
|
|
78
|
+
// Calculate statistics
|
|
79
|
+
const statistics = calculateStatistics(intervals);
|
|
80
|
+
return {
|
|
81
|
+
statistics,
|
|
82
|
+
uniqueCount,
|
|
83
|
+
allIdentical,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Score coefficient of variation
|
|
88
|
+
* Maps CV to human-likeness score using Gaussian
|
|
89
|
+
* Ideal CV is around 0.4-0.5 (moderate variation)
|
|
90
|
+
*/
|
|
91
|
+
export function scoreCoefficientOfVariation(cv) {
|
|
92
|
+
// Too low (<0.1) = bot-like (too consistent)
|
|
93
|
+
// Too high (>1.5) = random noise
|
|
94
|
+
// Ideal = 0.4-0.6 (natural human variation)
|
|
95
|
+
if (cv < 0.05)
|
|
96
|
+
return 0.1; // Almost zero variation - definitely bot
|
|
97
|
+
if (cv > 2.0)
|
|
98
|
+
return 0.3; // Too random
|
|
99
|
+
// Gaussian centered at 0.45 with width of 0.35
|
|
100
|
+
return gaussian(cv, 0.45, 0.35);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Score entropy - Higher is better (more random = more human)
|
|
104
|
+
* Uses sigmoid to smoothly map entropy to score
|
|
105
|
+
*/
|
|
106
|
+
export function scoreEntropy(normalizedEntropy) {
|
|
107
|
+
// Entropy of 0.7+ is very human
|
|
108
|
+
// Entropy < 0.3 is very bot-like
|
|
109
|
+
return sigmoid(normalizedEntropy, 0.5, 8); // Steep curve at midpoint 0.5
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Score autocorrelation - Lower is better (less periodic = more human)
|
|
113
|
+
*/
|
|
114
|
+
export function scoreAutocorrelation(autocorr) {
|
|
115
|
+
// Low autocorrelation (<0.2) is natural
|
|
116
|
+
// High autocorrelation (>0.6) is periodic/bot-like
|
|
117
|
+
return inverseSigmoid(autocorr, 0.4, 10);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Score jerk - Lower is better (smoother = more human)
|
|
121
|
+
*/
|
|
122
|
+
export function scoreJerk(avgJerk) {
|
|
123
|
+
// Human jerk typically < 0.01
|
|
124
|
+
// Bot jerk often > 0.1
|
|
125
|
+
// Use inverse sigmoid
|
|
126
|
+
return inverseSigmoid(avgJerk, 0.05, 50);
|
|
127
|
+
}
|
|
@@ -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
|
+
}
|