@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,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';