@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,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,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mathematical utility functions for continuous scoring
|
|
4
|
+
* Replaces magic number if-else chains with smooth mathematical functions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.sigmoid = sigmoid;
|
|
8
|
+
exports.inverseSigmoid = inverseSigmoid;
|
|
9
|
+
exports.gaussian = gaussian;
|
|
10
|
+
exports.exponentialDecay = exponentialDecay;
|
|
11
|
+
exports.normalize = normalize;
|
|
12
|
+
exports.clamp01 = clamp01;
|
|
13
|
+
exports.calculateStatistics = calculateStatistics;
|
|
14
|
+
exports.analyzeIntervals = analyzeIntervals;
|
|
15
|
+
exports.scoreCoefficientOfVariation = scoreCoefficientOfVariation;
|
|
16
|
+
exports.scoreEntropy = scoreEntropy;
|
|
17
|
+
exports.scoreAutocorrelation = scoreAutocorrelation;
|
|
18
|
+
exports.scoreJerk = scoreJerk;
|
|
19
|
+
/**
|
|
20
|
+
* Sigmoid function - Maps any value to [0, 1] with smooth S-curve
|
|
21
|
+
* @param x - Input value
|
|
22
|
+
* @param midpoint - Value that maps to 0.5
|
|
23
|
+
* @param steepness - How steep the curve is (higher = steeper)
|
|
24
|
+
*/
|
|
25
|
+
function sigmoid(x, midpoint = 0, steepness = 1) {
|
|
26
|
+
return 1 / (1 + Math.exp(-steepness * (x - midpoint)));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inverse sigmoid - High values map to low scores
|
|
30
|
+
*/
|
|
31
|
+
function inverseSigmoid(x, midpoint = 0, steepness = 1) {
|
|
32
|
+
return 1 - sigmoid(x, midpoint, steepness);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Gaussian (bell curve) - Peak at ideal value, falls off on both sides
|
|
36
|
+
* @param x - Input value
|
|
37
|
+
* @param ideal - Optimal value (peak of curve)
|
|
38
|
+
* @param width - How wide the acceptable range is
|
|
39
|
+
*/
|
|
40
|
+
function gaussian(x, ideal = 0, width = 1) {
|
|
41
|
+
const exponent = -Math.pow(x - ideal, 2) / (2 * width * width);
|
|
42
|
+
return Math.exp(exponent);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Exponential decay - High values get penalized exponentially
|
|
46
|
+
*/
|
|
47
|
+
function exponentialDecay(x, decayRate = 0.1) {
|
|
48
|
+
return Math.exp(-decayRate * x);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Normalize value to [0, 1] range
|
|
52
|
+
*/
|
|
53
|
+
function normalize(value, min = 0, max = 1) {
|
|
54
|
+
if (max === min)
|
|
55
|
+
return 0.5;
|
|
56
|
+
return Math.max(0, Math.min(1, (value - min) / (max - min)));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Clamp value to [0, 1]
|
|
60
|
+
*/
|
|
61
|
+
function clamp01(value) {
|
|
62
|
+
return Math.max(0, Math.min(1, value));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Calculate statistical metrics for an array of numbers
|
|
66
|
+
* @param values - Array of numerical values
|
|
67
|
+
* @returns Statistical metrics (mean, variance, stdDev, cv)
|
|
68
|
+
*/
|
|
69
|
+
function calculateStatistics(values) {
|
|
70
|
+
if (values.length === 0) {
|
|
71
|
+
return { mean: 0, variance: 0, stdDev: 0, cv: 0 };
|
|
72
|
+
}
|
|
73
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
74
|
+
const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
|
|
75
|
+
const stdDev = Math.sqrt(variance);
|
|
76
|
+
const cv = mean > 0 ? stdDev / mean : 0;
|
|
77
|
+
return { mean, variance, stdDev, cv };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Analyze timing intervals for bot-like patterns
|
|
81
|
+
* Returns raw analysis data without scoring - caller decides heuristics
|
|
82
|
+
*
|
|
83
|
+
* @param intervals - Array of time intervals (ms)
|
|
84
|
+
* @returns Analysis of interval patterns including stats and uniqueness
|
|
85
|
+
*/
|
|
86
|
+
function analyzeIntervals(intervals) {
|
|
87
|
+
if (intervals.length < 2)
|
|
88
|
+
return undefined;
|
|
89
|
+
// Check for identical intervals (round to 10ms to account for floating point)
|
|
90
|
+
const uniqueCount = new Set(intervals.map(i => Math.round(i / 10))).size;
|
|
91
|
+
const allIdentical = uniqueCount === 1;
|
|
92
|
+
// Calculate statistics
|
|
93
|
+
const statistics = calculateStatistics(intervals);
|
|
94
|
+
return {
|
|
95
|
+
statistics,
|
|
96
|
+
uniqueCount,
|
|
97
|
+
allIdentical,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Score coefficient of variation
|
|
102
|
+
* Maps CV to human-likeness score using Gaussian
|
|
103
|
+
* Ideal CV is around 0.4-0.5 (moderate variation)
|
|
104
|
+
*/
|
|
105
|
+
function scoreCoefficientOfVariation(cv) {
|
|
106
|
+
// Too low (<0.1) = bot-like (too consistent)
|
|
107
|
+
// Too high (>1.5) = random noise
|
|
108
|
+
// Ideal = 0.4-0.6 (natural human variation)
|
|
109
|
+
if (cv < 0.05)
|
|
110
|
+
return 0.1; // Almost zero variation - definitely bot
|
|
111
|
+
if (cv > 2.0)
|
|
112
|
+
return 0.3; // Too random
|
|
113
|
+
// Gaussian centered at 0.45 with width of 0.35
|
|
114
|
+
return gaussian(cv, 0.45, 0.35);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Score entropy - Higher is better (more random = more human)
|
|
118
|
+
* Uses sigmoid to smoothly map entropy to score
|
|
119
|
+
*/
|
|
120
|
+
function scoreEntropy(normalizedEntropy) {
|
|
121
|
+
// Entropy of 0.7+ is very human
|
|
122
|
+
// Entropy < 0.3 is very bot-like
|
|
123
|
+
return sigmoid(normalizedEntropy, 0.5, 8); // Steep curve at midpoint 0.5
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Score autocorrelation - Lower is better (less periodic = more human)
|
|
127
|
+
*/
|
|
128
|
+
function scoreAutocorrelation(autocorr) {
|
|
129
|
+
// Low autocorrelation (<0.2) is natural
|
|
130
|
+
// High autocorrelation (>0.6) is periodic/bot-like
|
|
131
|
+
return inverseSigmoid(autocorr, 0.4, 10);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Score jerk - Lower is better (smoother = more human)
|
|
135
|
+
*/
|
|
136
|
+
function scoreJerk(avgJerk) {
|
|
137
|
+
// Human jerk typically < 0.01
|
|
138
|
+
// Bot jerk often > 0.1
|
|
139
|
+
// Use inverse sigmoid
|
|
140
|
+
return inverseSigmoid(avgJerk, 0.05, 50);
|
|
141
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Click Behavior Detection Strategy
|
|
4
|
+
* Monitors specific elements for click positioning patterns
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ClickStrategy = void 0;
|
|
8
|
+
const strategy_1 = require("../strategy");
|
|
9
|
+
class ClickStrategy extends strategy_1.BaseStrategy {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super();
|
|
12
|
+
this.name = 'click';
|
|
13
|
+
this.defaultWeight = 0.30;
|
|
14
|
+
this.events = [];
|
|
15
|
+
this.targetSelectors = ['button', 'a', 'input[type="submit"]', '[role="button"]'];
|
|
16
|
+
this.lastMousePosition = null;
|
|
17
|
+
this.mouseListener = null;
|
|
18
|
+
this.clickListeners = new Map();
|
|
19
|
+
this.isActive = false;
|
|
20
|
+
if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
|
|
21
|
+
this.targetSelectors = options.targetSelectors;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
start() {
|
|
25
|
+
if (this.isActive)
|
|
26
|
+
return;
|
|
27
|
+
this.isActive = true;
|
|
28
|
+
// Track mouse position globally
|
|
29
|
+
this.mouseListener = (e) => {
|
|
30
|
+
const mouseEvent = e;
|
|
31
|
+
this.lastMousePosition = {
|
|
32
|
+
x: mouseEvent.clientX,
|
|
33
|
+
y: mouseEvent.clientY,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
document.addEventListener('mousemove', this.mouseListener, { passive: true });
|
|
37
|
+
// Find all matching elements and attach listeners
|
|
38
|
+
this.attachClickListeners();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Add a new target selector at runtime
|
|
42
|
+
*/
|
|
43
|
+
addTarget(selector) {
|
|
44
|
+
if (!this.targetSelectors.includes(selector)) {
|
|
45
|
+
this.targetSelectors.push(selector);
|
|
46
|
+
// Attach listeners if already active
|
|
47
|
+
if (this.isActive) {
|
|
48
|
+
this.attachClickListenersForSelector(selector);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
attachClickListeners() {
|
|
53
|
+
this.targetSelectors.forEach(selector => {
|
|
54
|
+
this.attachClickListenersForSelector(selector);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
attachClickListenersForSelector(selector) {
|
|
58
|
+
const elements = document.querySelectorAll(selector);
|
|
59
|
+
elements.forEach(element => {
|
|
60
|
+
if (this.clickListeners.has(element))
|
|
61
|
+
return; // Already attached
|
|
62
|
+
const listener = (e) => {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
const clickEvent = e;
|
|
65
|
+
const rect = element.getBoundingClientRect();
|
|
66
|
+
// Check if element is in viewport
|
|
67
|
+
const inViewport = (rect.top >= 0 &&
|
|
68
|
+
rect.left >= 0 &&
|
|
69
|
+
rect.bottom <= window.innerHeight &&
|
|
70
|
+
rect.right <= window.innerWidth);
|
|
71
|
+
// Analyze click position
|
|
72
|
+
let position;
|
|
73
|
+
if (!this.lastMousePosition) {
|
|
74
|
+
position = 'no-mouse-data'; // Bot - no mouse movement
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const mx = this.lastMousePosition.x;
|
|
78
|
+
const my = this.lastMousePosition.y;
|
|
79
|
+
// Check if mouse was over element
|
|
80
|
+
const overElement = (mx >= rect.left &&
|
|
81
|
+
mx <= rect.right &&
|
|
82
|
+
my >= rect.top &&
|
|
83
|
+
my <= rect.bottom);
|
|
84
|
+
if (!overElement) {
|
|
85
|
+
position = 'outside'; // Bot - mouse not over target
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Check if dead center (suspicious)
|
|
89
|
+
const centerX = rect.left + rect.width / 2;
|
|
90
|
+
const centerY = rect.top + rect.height / 2;
|
|
91
|
+
const distanceFromCenter = Math.sqrt((mx - centerX) ** 2 + (my - centerY) ** 2);
|
|
92
|
+
// Dead center = within 2px of center
|
|
93
|
+
if (distanceFromCenter < 2) {
|
|
94
|
+
position = 'dead-center'; // Suspicious - too perfect
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
position = 'over-element'; // Human - somewhere on the button
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.events.push({
|
|
102
|
+
clickX: clickEvent.clientX,
|
|
103
|
+
clickY: clickEvent.clientY,
|
|
104
|
+
mouseX: (_a = this.lastMousePosition) === null || _a === void 0 ? void 0 : _a.x,
|
|
105
|
+
mouseY: (_b = this.lastMousePosition) === null || _b === void 0 ? void 0 : _b.y,
|
|
106
|
+
rect,
|
|
107
|
+
inViewport,
|
|
108
|
+
position,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
});
|
|
111
|
+
// Notify detector - clicks are high-value events
|
|
112
|
+
this.notifyEvent(1.0);
|
|
113
|
+
};
|
|
114
|
+
element.addEventListener('click', listener);
|
|
115
|
+
this.clickListeners.set(element, listener);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
stop() {
|
|
119
|
+
if (!this.isActive)
|
|
120
|
+
return;
|
|
121
|
+
this.isActive = false;
|
|
122
|
+
// Remove mouse listener
|
|
123
|
+
if (this.mouseListener) {
|
|
124
|
+
document.removeEventListener('mousemove', this.mouseListener);
|
|
125
|
+
this.mouseListener = null;
|
|
126
|
+
}
|
|
127
|
+
// Remove all click listeners
|
|
128
|
+
this.clickListeners.forEach((listener, element) => {
|
|
129
|
+
element.removeEventListener('click', listener);
|
|
130
|
+
});
|
|
131
|
+
this.clickListeners.clear();
|
|
132
|
+
}
|
|
133
|
+
reset() {
|
|
134
|
+
this.events = [];
|
|
135
|
+
this.lastMousePosition = null;
|
|
136
|
+
}
|
|
137
|
+
score() {
|
|
138
|
+
if (this.events.length === 0)
|
|
139
|
+
return undefined;
|
|
140
|
+
let totalScore = 0;
|
|
141
|
+
for (const click of this.events) {
|
|
142
|
+
switch (click.position) {
|
|
143
|
+
case 'no-mouse-data':
|
|
144
|
+
totalScore += 0.0; // Bot - no mouse movement
|
|
145
|
+
break;
|
|
146
|
+
case 'outside':
|
|
147
|
+
totalScore += 0.0; // Bot - mouse not over target
|
|
148
|
+
break;
|
|
149
|
+
case 'dead-center':
|
|
150
|
+
totalScore += 0.5; // Suspicious - too perfect
|
|
151
|
+
break;
|
|
152
|
+
case 'over-element':
|
|
153
|
+
totalScore += 1.0; // Human - natural click
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return totalScore / this.events.length;
|
|
158
|
+
}
|
|
159
|
+
getDebugInfo() {
|
|
160
|
+
return {
|
|
161
|
+
eventCount: this.events.length,
|
|
162
|
+
positions: {
|
|
163
|
+
noMouseData: this.events.filter(e => e.position === 'no-mouse-data').length,
|
|
164
|
+
outside: this.events.filter(e => e.position === 'outside').length,
|
|
165
|
+
deadCenter: this.events.filter(e => e.position === 'dead-center').length,
|
|
166
|
+
overElement: this.events.filter(e => e.position === 'over-element').length,
|
|
167
|
+
},
|
|
168
|
+
inViewport: this.events.filter(e => e.inViewport).length,
|
|
169
|
+
trackedElements: this.clickListeners.size,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
exports.ClickStrategy = ClickStrategy;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Fingerprinting Strategy
|
|
3
|
+
* Captures browser environment on initialization and tick updates
|
|
4
|
+
*/
|
|
5
|
+
import { BaseStrategy } from '../strategy';
|
|
6
|
+
interface EnvironmentData {
|
|
7
|
+
screenWidth: number;
|
|
8
|
+
screenHeight: number;
|
|
9
|
+
windowWidth: number;
|
|
10
|
+
windowHeight: number;
|
|
11
|
+
devicePixelRatio: number;
|
|
12
|
+
colorDepth: number;
|
|
13
|
+
userAgent: string;
|
|
14
|
+
platform: string;
|
|
15
|
+
language: string;
|
|
16
|
+
languages: string[];
|
|
17
|
+
hardwareConcurrency?: number;
|
|
18
|
+
maxTouchPoints: number;
|
|
19
|
+
vendor: string;
|
|
20
|
+
hasWebGL: boolean;
|
|
21
|
+
hasWebRTC: boolean;
|
|
22
|
+
hasLocalStorage: boolean;
|
|
23
|
+
hasSessionStorage: boolean;
|
|
24
|
+
hasIndexedDB: boolean;
|
|
25
|
+
plugins: number;
|
|
26
|
+
mimeTypes: number;
|
|
27
|
+
suspiciousRatio: boolean;
|
|
28
|
+
suspiciousDimensions: boolean;
|
|
29
|
+
featureInconsistency: boolean;
|
|
30
|
+
isMobile: boolean;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
}
|
|
33
|
+
export declare class EnvironmentStrategy extends BaseStrategy {
|
|
34
|
+
readonly name = "environment";
|
|
35
|
+
readonly defaultWeight = 0.08;
|
|
36
|
+
private data;
|
|
37
|
+
start(): void;
|
|
38
|
+
stop(): void;
|
|
39
|
+
reset(): void;
|
|
40
|
+
onTick(_timestamp: number): void;
|
|
41
|
+
score(): number | undefined;
|
|
42
|
+
private isMobileDevice;
|
|
43
|
+
private captureEnvironment;
|
|
44
|
+
getDebugInfo(): EnvironmentData | null;
|
|
45
|
+
/**
|
|
46
|
+
* Check if current device is mobile
|
|
47
|
+
* Returns true if mobile, false otherwise
|
|
48
|
+
* Returns null if environment hasn't been captured yet
|
|
49
|
+
*/
|
|
50
|
+
isMobile(): boolean | null;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Environment Fingerprinting Strategy
|
|
4
|
+
* Captures browser environment on initialization and tick updates
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.EnvironmentStrategy = void 0;
|
|
8
|
+
const strategy_1 = require("../strategy");
|
|
9
|
+
const math_utils_1 = require("../math-utils");
|
|
10
|
+
class EnvironmentStrategy extends strategy_1.BaseStrategy {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.name = 'environment';
|
|
14
|
+
this.defaultWeight = 0.08;
|
|
15
|
+
this.data = null;
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
this.captureEnvironment();
|
|
19
|
+
}
|
|
20
|
+
stop() {
|
|
21
|
+
// Environment data persists
|
|
22
|
+
}
|
|
23
|
+
reset() {
|
|
24
|
+
this.data = null;
|
|
25
|
+
}
|
|
26
|
+
onTick(_timestamp) {
|
|
27
|
+
// Re-capture environment periodically to detect changes
|
|
28
|
+
this.captureEnvironment();
|
|
29
|
+
}
|
|
30
|
+
score() {
|
|
31
|
+
if (!this.data)
|
|
32
|
+
return undefined;
|
|
33
|
+
const env = this.data;
|
|
34
|
+
let score = 0;
|
|
35
|
+
let factors = 0;
|
|
36
|
+
// Suspicious indicators
|
|
37
|
+
score += env.suspiciousDimensions ? 0.1 : 1.0;
|
|
38
|
+
score += env.suspiciousRatio ? 0.2 : 1.0;
|
|
39
|
+
score += env.featureInconsistency ? 0.3 : 1.0;
|
|
40
|
+
factors += 3;
|
|
41
|
+
// Browser features
|
|
42
|
+
const featureCount = [
|
|
43
|
+
env.hasWebGL,
|
|
44
|
+
env.hasLocalStorage,
|
|
45
|
+
env.hasSessionStorage,
|
|
46
|
+
env.hasIndexedDB,
|
|
47
|
+
].filter(Boolean).length;
|
|
48
|
+
score += featureCount / 4;
|
|
49
|
+
factors++;
|
|
50
|
+
// Plugins
|
|
51
|
+
score += (0, math_utils_1.inverseSigmoid)(env.plugins, -2, -0.5);
|
|
52
|
+
score += (env.plugins > 0 ? 1.0 : 0.1);
|
|
53
|
+
factors += 2;
|
|
54
|
+
// Device
|
|
55
|
+
score += (0, math_utils_1.gaussian)(env.devicePixelRatio, 2, 1.5);
|
|
56
|
+
score += (env.colorDepth === 24 || env.colorDepth === 32) ? 1.0 : 0.4;
|
|
57
|
+
factors += 2;
|
|
58
|
+
return factors > 0 ? score / factors : undefined;
|
|
59
|
+
}
|
|
60
|
+
isMobileDevice() {
|
|
61
|
+
// Multiple checks for mobile detection
|
|
62
|
+
const hasTouchScreen = navigator.maxTouchPoints > 0 || 'ontouchstart' in window;
|
|
63
|
+
const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
64
|
+
const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
|
|
65
|
+
return (hasTouchScreen && smallScreen) || mobileUA;
|
|
66
|
+
}
|
|
67
|
+
captureEnvironment() {
|
|
68
|
+
try {
|
|
69
|
+
// WebGL detection
|
|
70
|
+
let hasWebGL = false;
|
|
71
|
+
try {
|
|
72
|
+
const canvas = document.createElement('canvas');
|
|
73
|
+
hasWebGL = !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
hasWebGL = false;
|
|
77
|
+
}
|
|
78
|
+
// WebRTC detection
|
|
79
|
+
const hasWebRTC = !!(window.RTCPeerConnection ||
|
|
80
|
+
window.mozRTCPeerConnection ||
|
|
81
|
+
window.webkitRTCPeerConnection);
|
|
82
|
+
// Mobile detection
|
|
83
|
+
const isMobile = this.isMobileDevice();
|
|
84
|
+
const windowWidth = window.innerWidth;
|
|
85
|
+
const windowHeight = window.innerHeight;
|
|
86
|
+
const screenWidth = window.screen.width;
|
|
87
|
+
const screenHeight = window.screen.height;
|
|
88
|
+
// Suspicious dimensions
|
|
89
|
+
const suspiciousDimensions = (windowWidth === 800 && windowHeight === 600) ||
|
|
90
|
+
(windowWidth === 1024 && windowHeight === 768) ||
|
|
91
|
+
(windowWidth === 1280 && windowHeight === 720) ||
|
|
92
|
+
(screenWidth === 800 && screenHeight === 600);
|
|
93
|
+
// Suspicious ratio
|
|
94
|
+
const windowScreenRatio = (windowWidth * windowHeight) / (screenWidth * screenHeight);
|
|
95
|
+
const suspiciousRatio = windowScreenRatio === 1.0 ||
|
|
96
|
+
windowScreenRatio < 0.1 ||
|
|
97
|
+
windowScreenRatio > 1.0;
|
|
98
|
+
// Feature inconsistency
|
|
99
|
+
const hasStorage = typeof localStorage !== 'undefined' && typeof sessionStorage !== 'undefined';
|
|
100
|
+
const featureInconsistency = (navigator.plugins.length === 0 && navigator.mimeTypes.length === 0) ||
|
|
101
|
+
!hasWebGL ||
|
|
102
|
+
!hasStorage;
|
|
103
|
+
this.data = {
|
|
104
|
+
screenWidth,
|
|
105
|
+
screenHeight,
|
|
106
|
+
windowWidth,
|
|
107
|
+
windowHeight,
|
|
108
|
+
devicePixelRatio: window.devicePixelRatio,
|
|
109
|
+
colorDepth: window.screen.colorDepth,
|
|
110
|
+
userAgent: navigator.userAgent,
|
|
111
|
+
platform: navigator.platform,
|
|
112
|
+
language: navigator.language,
|
|
113
|
+
languages: navigator.languages ? Array.from(navigator.languages) : [navigator.language],
|
|
114
|
+
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
115
|
+
maxTouchPoints: navigator.maxTouchPoints || 0,
|
|
116
|
+
vendor: navigator.vendor,
|
|
117
|
+
hasWebGL,
|
|
118
|
+
hasWebRTC,
|
|
119
|
+
hasLocalStorage: typeof localStorage !== 'undefined',
|
|
120
|
+
hasSessionStorage: typeof sessionStorage !== 'undefined',
|
|
121
|
+
hasIndexedDB: 'indexedDB' in window,
|
|
122
|
+
plugins: navigator.plugins.length,
|
|
123
|
+
mimeTypes: navigator.mimeTypes.length,
|
|
124
|
+
suspiciousRatio,
|
|
125
|
+
suspiciousDimensions,
|
|
126
|
+
featureInconsistency,
|
|
127
|
+
isMobile,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.warn('Failed to capture environment:', error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
getDebugInfo() {
|
|
136
|
+
return this.data;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if current device is mobile
|
|
140
|
+
* Returns true if mobile, false otherwise
|
|
141
|
+
* Returns null if environment hasn't been captured yet
|
|
142
|
+
*/
|
|
143
|
+
isMobile() {
|
|
144
|
+
var _a, _b;
|
|
145
|
+
return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.isMobile) !== null && _b !== void 0 ? _b : null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.EnvironmentStrategy = EnvironmentStrategy;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection Strategies - Modular behavior analysis
|
|
3
|
+
* Import only what you need for optimal bundle size
|
|
4
|
+
*/
|
|
5
|
+
export { MouseStrategy } from './mouse';
|
|
6
|
+
export { ScrollStrategy } from './scroll';
|
|
7
|
+
export { ClickStrategy } from './click';
|
|
8
|
+
export { TapStrategy } from './tap';
|
|
9
|
+
export { KeyboardStrategy } from './keyboard';
|
|
10
|
+
export { EnvironmentStrategy } from './environment';
|
|
11
|
+
export { ResizeStrategy } from './resize';
|
|
12
|
+
export { MouseStrategy as Mouse } from './mouse';
|
|
13
|
+
export { ScrollStrategy as Scroll } from './scroll';
|
|
14
|
+
export { ClickStrategy as Click } from './click';
|
|
15
|
+
export { TapStrategy as Tap } from './tap';
|
|
16
|
+
export { KeyboardStrategy as Keyboard } from './keyboard';
|
|
17
|
+
export { EnvironmentStrategy as Environment } from './environment';
|
|
18
|
+
export { ResizeStrategy as Resize } from './resize';
|