@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,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,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_SETTINGS = void 0;
|
|
4
|
+
exports.DEFAULT_SETTINGS = {
|
|
5
|
+
sampleRates: {
|
|
6
|
+
mouseMove: 0.1, // Track 10% of mouse moves
|
|
7
|
+
scroll: 1.0, // Track ALL scrolls - critical for detection
|
|
8
|
+
keypress: 1.0, // Track all keypresses
|
|
9
|
+
},
|
|
10
|
+
rollingWindows: {
|
|
11
|
+
mouseMove: 30000,
|
|
12
|
+
scroll: 30000,
|
|
13
|
+
},
|
|
14
|
+
weights: {
|
|
15
|
+
mouseMovement: 0.30, // Increased - continuous behavioral signal
|
|
16
|
+
clickAccuracy: 0.30, // Increased - critical behavioral signal
|
|
17
|
+
scrollBehavior: 0.15, // Unchanged
|
|
18
|
+
keyboardTiming: 0.10, // Slightly reduced
|
|
19
|
+
tabActivity: 0.05, // Unchanged
|
|
20
|
+
resizeBehavior: 0.02, // Reduced - less reliable
|
|
21
|
+
environmentFingerprint: 0.08, // Reduced - static signal, one-time check
|
|
22
|
+
},
|
|
23
|
+
customScorers: {},
|
|
24
|
+
clickMouseHistoryWindow: 1000,
|
|
25
|
+
useWebWorker: false, // Will implement Web Worker support in phase 2
|
|
26
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ScoreOptions, ScoreResult } from './types';
|
|
2
|
+
import type { DetectionStrategy, StrategyConfig, TickOptions } from './strategy';
|
|
3
|
+
/**
|
|
4
|
+
* Main class for behavior detection
|
|
5
|
+
* Uses modular strategy pattern for tree-shakeable, autonomous detection modules
|
|
6
|
+
*/
|
|
7
|
+
export declare class BehaviorDetector {
|
|
8
|
+
private strategies;
|
|
9
|
+
private isTracking;
|
|
10
|
+
private isPausedByVisibility;
|
|
11
|
+
private tickInterval;
|
|
12
|
+
private tickIntervalMs;
|
|
13
|
+
private pauseOnHidden;
|
|
14
|
+
private visibilityChangeHandler;
|
|
15
|
+
private confidenceScore;
|
|
16
|
+
private readonly CONFIDENCE_TARGET;
|
|
17
|
+
private readonly CONFIDENCE_DECAY;
|
|
18
|
+
constructor(tickOptions?: TickOptions);
|
|
19
|
+
/**
|
|
20
|
+
* Add a detection strategy
|
|
21
|
+
*/
|
|
22
|
+
addStrategy(strategy: DetectionStrategy, weight?: number): this;
|
|
23
|
+
/**
|
|
24
|
+
* Handle event from strategy - update confidence
|
|
25
|
+
*/
|
|
26
|
+
private onStrategyEvent;
|
|
27
|
+
/**
|
|
28
|
+
* Remove a detection strategy
|
|
29
|
+
*/
|
|
30
|
+
removeStrategy(name: string): this;
|
|
31
|
+
/**
|
|
32
|
+
* Enable/disable a strategy
|
|
33
|
+
*/
|
|
34
|
+
setStrategyEnabled(name: string, enabled: boolean): this;
|
|
35
|
+
/**
|
|
36
|
+
* Get all registered strategies
|
|
37
|
+
*/
|
|
38
|
+
getStrategies(): ReadonlyMap<string, StrategyConfig>;
|
|
39
|
+
/**
|
|
40
|
+
* Start tracking
|
|
41
|
+
*/
|
|
42
|
+
start(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Stop tracking
|
|
45
|
+
*/
|
|
46
|
+
stop(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Reset all data
|
|
49
|
+
*/
|
|
50
|
+
reset(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Calculate human likelihood score
|
|
53
|
+
*/
|
|
54
|
+
score(options?: ScoreOptions): Promise<ScoreResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if currently tracking
|
|
57
|
+
*/
|
|
58
|
+
isActive(): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Check if currently paused due to tab visibility
|
|
61
|
+
*/
|
|
62
|
+
isPaused(): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Get event count from all strategies
|
|
65
|
+
*/
|
|
66
|
+
getEventCount(): Record<string, number>;
|
|
67
|
+
/**
|
|
68
|
+
* Get debug info from all strategies
|
|
69
|
+
*/
|
|
70
|
+
getStrategyDebugInfo(): Record<string, any>;
|
|
71
|
+
/**
|
|
72
|
+
* Get current confidence score (0-1)
|
|
73
|
+
* Represents how much interaction data we've collected
|
|
74
|
+
* Higher confidence = more reliable classification
|
|
75
|
+
*/
|
|
76
|
+
getConfidence(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Check if confidence is above threshold for reliable classification
|
|
79
|
+
* @param threshold - Minimum confidence (0-1), default 0.3
|
|
80
|
+
*/
|
|
81
|
+
hasConfidentData(threshold?: number): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Cleanup resources
|
|
84
|
+
*/
|
|
85
|
+
destroy(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Handle visibility change events
|
|
88
|
+
*/
|
|
89
|
+
private handleVisibilityChange;
|
|
90
|
+
/**
|
|
91
|
+
* Start tick mechanism for strategies
|
|
92
|
+
*/
|
|
93
|
+
private startTick;
|
|
94
|
+
/**
|
|
95
|
+
* Stop tick mechanism
|
|
96
|
+
*/
|
|
97
|
+
private stopTick;
|
|
98
|
+
/**
|
|
99
|
+
* Calculate score using strategies
|
|
100
|
+
*/
|
|
101
|
+
private calculateStrategyScore;
|
|
102
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main class for behavior detection
|
|
3
|
+
* Uses modular strategy pattern for tree-shakeable, autonomous detection modules
|
|
4
|
+
*/
|
|
5
|
+
export class BehaviorDetector {
|
|
6
|
+
constructor(tickOptions) {
|
|
7
|
+
// Strategy mode
|
|
8
|
+
this.strategies = new Map();
|
|
9
|
+
this.isTracking = false;
|
|
10
|
+
this.isPausedByVisibility = false;
|
|
11
|
+
this.tickInterval = null;
|
|
12
|
+
this.tickIntervalMs = 1000; // Default: 1 second
|
|
13
|
+
this.pauseOnHidden = true;
|
|
14
|
+
this.visibilityChangeHandler = null;
|
|
15
|
+
// Confidence tracking
|
|
16
|
+
this.confidenceScore = 0;
|
|
17
|
+
this.CONFIDENCE_TARGET = 1.0; // Target confidence for reliable classification
|
|
18
|
+
this.CONFIDENCE_DECAY = 0.95; // Per event decay to prevent infinite growth
|
|
19
|
+
if (tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.interval) {
|
|
20
|
+
this.tickIntervalMs = tickOptions.interval;
|
|
21
|
+
}
|
|
22
|
+
if ((tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.pauseOnHidden) !== undefined) {
|
|
23
|
+
this.pauseOnHidden = tickOptions.pauseOnHidden;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Add a detection strategy
|
|
28
|
+
*/
|
|
29
|
+
addStrategy(strategy, weight) {
|
|
30
|
+
const config = {
|
|
31
|
+
strategy,
|
|
32
|
+
weight: weight !== null && weight !== void 0 ? weight : strategy.defaultWeight,
|
|
33
|
+
enabled: true,
|
|
34
|
+
};
|
|
35
|
+
this.strategies.set(strategy.name, config);
|
|
36
|
+
// Set up event callback for confidence tracking
|
|
37
|
+
if (strategy.setEventCallback) {
|
|
38
|
+
strategy.setEventCallback((event) => {
|
|
39
|
+
this.onStrategyEvent(event, config.weight);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// If already tracking, start this strategy immediately
|
|
43
|
+
if (this.isTracking) {
|
|
44
|
+
strategy.start();
|
|
45
|
+
}
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Handle event from strategy - update confidence
|
|
50
|
+
*/
|
|
51
|
+
onStrategyEvent(event, strategyWeight) {
|
|
52
|
+
// Apply decay to existing confidence to prevent infinite growth
|
|
53
|
+
this.confidenceScore *= this.CONFIDENCE_DECAY;
|
|
54
|
+
// Add weighted contribution from this event
|
|
55
|
+
// eventWeight (0-1) * strategyWeight (e.g. 0.3 for mouse) = contribution
|
|
56
|
+
const contribution = event.weight * strategyWeight;
|
|
57
|
+
this.confidenceScore = Math.min(this.CONFIDENCE_TARGET, this.confidenceScore + contribution);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove a detection strategy
|
|
61
|
+
*/
|
|
62
|
+
removeStrategy(name) {
|
|
63
|
+
const config = this.strategies.get(name);
|
|
64
|
+
if (config) {
|
|
65
|
+
if (this.isTracking) {
|
|
66
|
+
config.strategy.stop();
|
|
67
|
+
}
|
|
68
|
+
this.strategies.delete(name);
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Enable/disable a strategy
|
|
74
|
+
*/
|
|
75
|
+
setStrategyEnabled(name, enabled) {
|
|
76
|
+
const config = this.strategies.get(name);
|
|
77
|
+
if (config) {
|
|
78
|
+
config.enabled = enabled;
|
|
79
|
+
// If tracking and being disabled, stop it
|
|
80
|
+
if (!enabled && this.isTracking) {
|
|
81
|
+
config.strategy.stop();
|
|
82
|
+
}
|
|
83
|
+
// If tracking and being enabled, start it
|
|
84
|
+
if (enabled && this.isTracking) {
|
|
85
|
+
config.strategy.start();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get all registered strategies
|
|
92
|
+
*/
|
|
93
|
+
getStrategies() {
|
|
94
|
+
return this.strategies;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Start tracking
|
|
98
|
+
*/
|
|
99
|
+
start() {
|
|
100
|
+
if (this.isTracking)
|
|
101
|
+
return;
|
|
102
|
+
this.isTracking = true;
|
|
103
|
+
// Set up visibility change listener
|
|
104
|
+
if (this.pauseOnHidden && typeof document !== 'undefined') {
|
|
105
|
+
this.visibilityChangeHandler = this.handleVisibilityChange.bind(this);
|
|
106
|
+
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
107
|
+
// Check initial visibility state
|
|
108
|
+
if (document.hidden) {
|
|
109
|
+
this.isPausedByVisibility = true;
|
|
110
|
+
return; // Don't start strategies if already hidden
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Start all enabled strategies
|
|
114
|
+
for (const [_, config] of this.strategies) {
|
|
115
|
+
if (config.enabled) {
|
|
116
|
+
config.strategy.start();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Start tick mechanism
|
|
120
|
+
this.startTick();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Stop tracking
|
|
124
|
+
*/
|
|
125
|
+
stop() {
|
|
126
|
+
if (!this.isTracking)
|
|
127
|
+
return;
|
|
128
|
+
this.isTracking = false;
|
|
129
|
+
this.isPausedByVisibility = false;
|
|
130
|
+
// Remove visibility change listener
|
|
131
|
+
if (this.visibilityChangeHandler && typeof document !== 'undefined') {
|
|
132
|
+
document.removeEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
133
|
+
this.visibilityChangeHandler = null;
|
|
134
|
+
}
|
|
135
|
+
// Stop all strategies
|
|
136
|
+
for (const [_, config] of this.strategies) {
|
|
137
|
+
config.strategy.stop();
|
|
138
|
+
}
|
|
139
|
+
// Stop tick
|
|
140
|
+
this.stopTick();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Reset all data
|
|
144
|
+
*/
|
|
145
|
+
reset() {
|
|
146
|
+
this.confidenceScore = 0;
|
|
147
|
+
for (const [_, config] of this.strategies) {
|
|
148
|
+
config.strategy.reset();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Calculate human likelihood score
|
|
153
|
+
*/
|
|
154
|
+
async score(options = {}) {
|
|
155
|
+
const breakdown = this.calculateStrategyScore();
|
|
156
|
+
const result = {
|
|
157
|
+
score: breakdown.overall,
|
|
158
|
+
};
|
|
159
|
+
if (options.breakdown) {
|
|
160
|
+
result.breakdown = breakdown;
|
|
161
|
+
}
|
|
162
|
+
// Note: auditTrail not available in strategy mode
|
|
163
|
+
// Each strategy manages its own data independently
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if currently tracking
|
|
168
|
+
*/
|
|
169
|
+
isActive() {
|
|
170
|
+
return this.isTracking;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if currently paused due to tab visibility
|
|
174
|
+
*/
|
|
175
|
+
isPaused() {
|
|
176
|
+
return this.isPausedByVisibility;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get event count from all strategies
|
|
180
|
+
*/
|
|
181
|
+
getEventCount() {
|
|
182
|
+
var _a, _b;
|
|
183
|
+
const counts = {};
|
|
184
|
+
for (const [name, config] of this.strategies) {
|
|
185
|
+
const debug = (_b = (_a = config.strategy).getDebugInfo) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
186
|
+
if ((debug === null || debug === void 0 ? void 0 : debug.eventCount) !== undefined) {
|
|
187
|
+
counts[name] = debug.eventCount;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return counts;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get debug info from all strategies
|
|
194
|
+
*/
|
|
195
|
+
getStrategyDebugInfo() {
|
|
196
|
+
const debug = {};
|
|
197
|
+
for (const [name, config] of this.strategies) {
|
|
198
|
+
if (config.strategy.getDebugInfo) {
|
|
199
|
+
debug[name] = config.strategy.getDebugInfo();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return debug;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get current confidence score (0-1)
|
|
206
|
+
* Represents how much interaction data we've collected
|
|
207
|
+
* Higher confidence = more reliable classification
|
|
208
|
+
*/
|
|
209
|
+
getConfidence() {
|
|
210
|
+
return Math.min(1, this.confidenceScore);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Check if confidence is above threshold for reliable classification
|
|
214
|
+
* @param threshold - Minimum confidence (0-1), default 0.3
|
|
215
|
+
*/
|
|
216
|
+
hasConfidentData(threshold = 0.3) {
|
|
217
|
+
return this.getConfidence() >= threshold;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Cleanup resources
|
|
221
|
+
*/
|
|
222
|
+
destroy() {
|
|
223
|
+
this.stop();
|
|
224
|
+
this.strategies.clear();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Handle visibility change events
|
|
228
|
+
*/
|
|
229
|
+
handleVisibilityChange() {
|
|
230
|
+
if (!this.isTracking)
|
|
231
|
+
return;
|
|
232
|
+
if (document.hidden) {
|
|
233
|
+
// Tab became hidden - pause detection
|
|
234
|
+
if (!this.isPausedByVisibility) {
|
|
235
|
+
this.isPausedByVisibility = true;
|
|
236
|
+
// Stop all strategies
|
|
237
|
+
for (const [_, config] of this.strategies) {
|
|
238
|
+
if (config.enabled) {
|
|
239
|
+
config.strategy.stop();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Stop tick
|
|
243
|
+
this.stopTick();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Tab became visible - resume detection
|
|
248
|
+
if (this.isPausedByVisibility) {
|
|
249
|
+
this.isPausedByVisibility = false;
|
|
250
|
+
// Restart all enabled strategies
|
|
251
|
+
for (const [_, config] of this.strategies) {
|
|
252
|
+
if (config.enabled) {
|
|
253
|
+
config.strategy.start();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Restart tick
|
|
257
|
+
this.startTick();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Start tick mechanism for strategies
|
|
263
|
+
*/
|
|
264
|
+
startTick() {
|
|
265
|
+
if (this.tickInterval !== null)
|
|
266
|
+
return;
|
|
267
|
+
this.tickInterval = window.setInterval(() => {
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
for (const [_, config] of this.strategies) {
|
|
270
|
+
if (config.enabled && config.strategy.onTick) {
|
|
271
|
+
config.strategy.onTick(now);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}, this.tickIntervalMs);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Stop tick mechanism
|
|
278
|
+
*/
|
|
279
|
+
stopTick() {
|
|
280
|
+
if (this.tickInterval !== null) {
|
|
281
|
+
clearInterval(this.tickInterval);
|
|
282
|
+
this.tickInterval = null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Calculate score using strategies
|
|
287
|
+
*/
|
|
288
|
+
calculateStrategyScore() {
|
|
289
|
+
const factors = {};
|
|
290
|
+
const weights = {};
|
|
291
|
+
let totalWeight = 0;
|
|
292
|
+
let weightedSum = 0;
|
|
293
|
+
for (const [name, config] of this.strategies) {
|
|
294
|
+
if (!config.enabled)
|
|
295
|
+
continue;
|
|
296
|
+
const score = config.strategy.score();
|
|
297
|
+
factors[name] = score;
|
|
298
|
+
weights[name] = config.weight;
|
|
299
|
+
if (score !== undefined && score !== null) {
|
|
300
|
+
totalWeight += config.weight;
|
|
301
|
+
weightedSum += score * config.weight;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const overall = totalWeight > 0 ? weightedSum / totalWeight : 0.5;
|
|
305
|
+
return {
|
|
306
|
+
overall: Math.max(0, Math.min(1, overall)),
|
|
307
|
+
factors,
|
|
308
|
+
weights,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
export {};
|