@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,149 @@
1
+ "use strict";
2
+ /**
3
+ * Scroll Behavior Detection Strategy
4
+ * Autonomous module managing scroll event listeners
5
+ * Tracks normalized scroll distance and velocity patterns
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ScrollStrategy = void 0;
9
+ const strategy_1 = require("../strategy");
10
+ const math_utils_1 = require("../math-utils");
11
+ class ScrollStrategy extends strategy_1.BaseStrategy {
12
+ constructor(options) {
13
+ super();
14
+ this.name = 'scroll';
15
+ this.defaultWeight = 0.15;
16
+ this.distanceSeries = [];
17
+ this.velocitySeries = [];
18
+ this.lastScrollY = null;
19
+ this.lastTimestamp = 0;
20
+ this.rollingWindowMs = 5000;
21
+ this.documentHeight = 1;
22
+ this.listener = null;
23
+ this.isActive = false;
24
+ if ((options === null || options === void 0 ? void 0 : options.rollingWindow) !== undefined)
25
+ this.rollingWindowMs = options.rollingWindow;
26
+ }
27
+ start() {
28
+ if (this.isActive)
29
+ return;
30
+ this.isActive = true;
31
+ // Calculate document height for normalization
32
+ this.documentHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, 1);
33
+ this.listener = (_e) => {
34
+ const scrollY = window.scrollY;
35
+ const now = Date.now();
36
+ // Always update last position on first event
37
+ if (this.lastScrollY === null) {
38
+ this.lastScrollY = scrollY;
39
+ this.lastTimestamp = now;
40
+ return;
41
+ }
42
+ const pixelDelta = scrollY - this.lastScrollY;
43
+ const deltaTime = now - this.lastTimestamp;
44
+ // Skip if no actual scroll happened
45
+ if (pixelDelta === 0) {
46
+ return;
47
+ }
48
+ // Normalize scroll distance to document height (0-1 range)
49
+ const normalizedDistance = Math.abs(pixelDelta) / this.documentHeight;
50
+ // Record all scroll movements (no threshold - let pattern detection decide)
51
+ if (deltaTime > 0) {
52
+ const velocity = normalizedDistance / deltaTime; // normalized units per ms
53
+ // Store as time series
54
+ this.distanceSeries.push({ value: normalizedDistance, timestamp: now });
55
+ this.velocitySeries.push({ value: velocity, timestamp: now });
56
+ // Notify detector for confidence tracking
57
+ this.notifyEvent(Math.min(1, normalizedDistance * 10));
58
+ // Apply rolling window efficiently
59
+ const cutoff = now - this.rollingWindowMs;
60
+ while (this.distanceSeries.length > 0 && this.distanceSeries[0].timestamp < cutoff) {
61
+ this.distanceSeries.shift();
62
+ this.velocitySeries.shift();
63
+ }
64
+ }
65
+ this.lastScrollY = scrollY;
66
+ this.lastTimestamp = now;
67
+ };
68
+ // Listen on window for scroll events (document scrolling)
69
+ window.addEventListener('scroll', this.listener, { passive: true });
70
+ }
71
+ stop() {
72
+ if (!this.isActive)
73
+ return;
74
+ this.isActive = false;
75
+ if (this.listener) {
76
+ window.removeEventListener('scroll', this.listener);
77
+ this.listener = null;
78
+ }
79
+ }
80
+ reset() {
81
+ this.distanceSeries = [];
82
+ this.velocitySeries = [];
83
+ this.lastScrollY = null;
84
+ this.lastTimestamp = 0;
85
+ }
86
+ score() {
87
+ if (this.distanceSeries.length < 2)
88
+ return undefined;
89
+ // Scroll-specific pattern detection
90
+ return this.detectScrollPatterns();
91
+ }
92
+ /**
93
+ * Scroll-specific pattern detection
94
+ * Detects bot-like patterns: constant deltas, instant jumps, too smooth
95
+ */
96
+ detectScrollPatterns() {
97
+ if (this.distanceSeries.length < 2)
98
+ return undefined;
99
+ let score = 0;
100
+ let factors = 0;
101
+ // 1. IDENTICAL DELTAS - Smoking gun for bots
102
+ const distances = this.distanceSeries.map(p => p.value);
103
+ if (distances.length >= 2) {
104
+ // Check if all distances are identical (tolerance for floating point)
105
+ const uniqueDistances = new Set(distances.map(d => Math.round(d * 1000))).size;
106
+ if (uniqueDistances === 1) {
107
+ // All scroll amounts identical = definite bot
108
+ return 0.05;
109
+ }
110
+ }
111
+ // 2. DISTANCE CONSISTENCY - Bots often scroll same amount repeatedly
112
+ if (distances.length >= 2) {
113
+ const stats = (0, math_utils_1.calculateStatistics)(distances);
114
+ // Real humans have high CV (>0.8) due to natural scroll acceleration/deceleration
115
+ // Simple automation has lower CV even with varied amounts
116
+ // Gaussian centered high with narrow width - be strict
117
+ score += (0, math_utils_1.gaussian)(stats.cv, 1.0, 0.4);
118
+ factors++;
119
+ }
120
+ // 3. VELOCITY VARIATION - Humans have highly variable scroll speeds
121
+ const velocities = this.velocitySeries.map(p => p.value);
122
+ if (velocities.length >= 2) {
123
+ const stats = (0, math_utils_1.calculateStatistics)(velocities);
124
+ // Human velocity is very chaotic (acceleration/deceleration)
125
+ // Automation has more consistent velocity even with delays
126
+ score += (0, math_utils_1.gaussian)(stats.cv, 1.2, 0.5);
127
+ factors++;
128
+ }
129
+ // 4. INSTANT JUMPS - Detect programmatic scrollTo
130
+ const instantJumps = distances.filter(d => d > 0.1).length; // >10% of document in one event
131
+ if (distances.length > 0) {
132
+ const jumpRatio = instantJumps / distances.length;
133
+ // Penalize high jump ratio (programmatic scrollTo)
134
+ score += (0, math_utils_1.inverseSigmoid)(jumpRatio, 0.3, 15);
135
+ factors++;
136
+ }
137
+ return factors > 0 ? score / factors : undefined;
138
+ }
139
+ getDebugInfo() {
140
+ return {
141
+ eventCount: this.distanceSeries.length,
142
+ rollingWindow: this.rollingWindowMs,
143
+ isActive: this.isActive,
144
+ distanceSeries: this.distanceSeries,
145
+ velocitySeries: this.velocitySeries,
146
+ };
147
+ }
148
+ }
149
+ exports.ScrollStrategy = ScrollStrategy;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Tap Behavior Detection Strategy (Mobile)
3
+ * Monitors touch interactions on specific elements
4
+ * Analyzes tap duration, precision, movement, and timing patterns
5
+ */
6
+ import { BaseStrategy } from '../strategy';
7
+ export declare class TapStrategy extends BaseStrategy {
8
+ readonly name = "tap";
9
+ readonly defaultWeight = 0.35;
10
+ private events;
11
+ private targetSelectors;
12
+ private touchStartListeners;
13
+ private touchEndListeners;
14
+ private activeTouches;
15
+ private isActive;
16
+ constructor(options?: {
17
+ targetSelectors?: string[];
18
+ });
19
+ start(): void;
20
+ addTarget(selector: string): void;
21
+ private attachTouchListeners;
22
+ private attachTouchListenersForSelector;
23
+ stop(): void;
24
+ reset(): void;
25
+ score(): number | undefined;
26
+ getDebugInfo(): {
27
+ eventCount: number;
28
+ positions: {
29
+ outside: number;
30
+ deadCenter: number;
31
+ overElement: number;
32
+ };
33
+ durations: number[];
34
+ movements: number[];
35
+ inViewport: number;
36
+ trackedElements: number;
37
+ };
38
+ }
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ /**
3
+ * Tap Behavior Detection Strategy (Mobile)
4
+ * Monitors touch interactions on specific elements
5
+ * Analyzes tap duration, precision, movement, and timing patterns
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.TapStrategy = void 0;
9
+ const strategy_1 = require("../strategy");
10
+ const math_utils_1 = require("../math-utils");
11
+ class TapStrategy extends strategy_1.BaseStrategy {
12
+ constructor(options) {
13
+ super();
14
+ this.name = 'tap';
15
+ this.defaultWeight = 0.35;
16
+ this.events = [];
17
+ this.targetSelectors = ['button', 'a', 'input[type="submit"]', '[role="button"]'];
18
+ this.touchStartListeners = new Map();
19
+ this.touchEndListeners = new Map();
20
+ this.activeTouches = new Map();
21
+ this.isActive = false;
22
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
23
+ this.targetSelectors = options.targetSelectors;
24
+ }
25
+ }
26
+ start() {
27
+ if (this.isActive)
28
+ return;
29
+ this.isActive = true;
30
+ this.attachTouchListeners();
31
+ }
32
+ addTarget(selector) {
33
+ if (!this.targetSelectors.includes(selector)) {
34
+ this.targetSelectors.push(selector);
35
+ if (this.isActive) {
36
+ this.attachTouchListenersForSelector(selector);
37
+ }
38
+ }
39
+ }
40
+ attachTouchListeners() {
41
+ this.targetSelectors.forEach(selector => {
42
+ this.attachTouchListenersForSelector(selector);
43
+ });
44
+ }
45
+ attachTouchListenersForSelector(selector) {
46
+ const elements = document.querySelectorAll(selector);
47
+ elements.forEach(element => {
48
+ if (this.touchStartListeners.has(element))
49
+ return;
50
+ const startListener = (e) => {
51
+ const touchEvent = e;
52
+ // Only track first touch (ignore multi-touch)
53
+ if (touchEvent.touches.length !== 1)
54
+ return;
55
+ const touch = touchEvent.touches[0];
56
+ this.activeTouches.set(touch.identifier, {
57
+ x: touch.clientX,
58
+ y: touch.clientY,
59
+ timestamp: Date.now(),
60
+ element,
61
+ });
62
+ };
63
+ const endListener = (e) => {
64
+ const touchEvent = e;
65
+ if (touchEvent.changedTouches.length !== 1)
66
+ return;
67
+ const touch = touchEvent.changedTouches[0];
68
+ const startData = this.activeTouches.get(touch.identifier);
69
+ if (!startData || startData.element !== element)
70
+ return;
71
+ const now = Date.now();
72
+ const duration = now - startData.timestamp;
73
+ const dx = touch.clientX - startData.x;
74
+ const dy = touch.clientY - startData.y;
75
+ const movement = Math.sqrt(dx * dx + dy * dy);
76
+ const rect = element.getBoundingClientRect();
77
+ const inViewport = (rect.top >= 0 &&
78
+ rect.left >= 0 &&
79
+ rect.bottom <= window.innerHeight &&
80
+ rect.right <= window.innerWidth);
81
+ // Analyze tap position
82
+ const tx = touch.clientX;
83
+ const ty = touch.clientY;
84
+ const overElement = (tx >= rect.left &&
85
+ tx <= rect.right &&
86
+ ty >= rect.top &&
87
+ ty <= rect.bottom);
88
+ let position;
89
+ if (!overElement) {
90
+ position = 'outside';
91
+ }
92
+ else {
93
+ const centerX = rect.left + rect.width / 2;
94
+ const centerY = rect.top + rect.height / 2;
95
+ const distanceFromCenter = Math.sqrt((tx - centerX) ** 2 + (ty - centerY) ** 2);
96
+ // Dead center = within 2px
97
+ position = distanceFromCenter < 2 ? 'dead-center' : 'over-element';
98
+ }
99
+ this.events.push({
100
+ x: tx,
101
+ y: ty,
102
+ rect,
103
+ inViewport,
104
+ position,
105
+ duration,
106
+ movement,
107
+ timestamp: now,
108
+ });
109
+ // Notify detector - taps are high-value events
110
+ this.notifyEvent(1.0);
111
+ this.activeTouches.delete(touch.identifier);
112
+ };
113
+ element.addEventListener('touchstart', startListener, { passive: true });
114
+ element.addEventListener('touchend', endListener, { passive: true });
115
+ this.touchStartListeners.set(element, startListener);
116
+ this.touchEndListeners.set(element, endListener);
117
+ });
118
+ }
119
+ stop() {
120
+ if (!this.isActive)
121
+ return;
122
+ this.isActive = false;
123
+ this.touchStartListeners.forEach((listener, element) => {
124
+ element.removeEventListener('touchstart', listener);
125
+ });
126
+ this.touchStartListeners.clear();
127
+ this.touchEndListeners.forEach((listener, element) => {
128
+ element.removeEventListener('touchend', listener);
129
+ });
130
+ this.touchEndListeners.clear();
131
+ this.activeTouches.clear();
132
+ }
133
+ reset() {
134
+ this.events = [];
135
+ this.activeTouches.clear();
136
+ }
137
+ score() {
138
+ if (this.events.length === 0)
139
+ return undefined;
140
+ let score = 0;
141
+ let factors = 0;
142
+ // 1. Position accuracy
143
+ let positionScore = 0;
144
+ for (const tap of this.events) {
145
+ switch (tap.position) {
146
+ case 'outside':
147
+ positionScore += 0.0; // Bot
148
+ break;
149
+ case 'dead-center':
150
+ positionScore += 0.5; // Suspicious
151
+ break;
152
+ case 'over-element':
153
+ positionScore += 1.0; // Human
154
+ break;
155
+ }
156
+ }
157
+ score += positionScore / this.events.length;
158
+ factors++;
159
+ // 2. Tap duration variation - Humans have natural variation (50-150ms typical)
160
+ const durations = this.events.map(e => e.duration);
161
+ if (durations.length >= 2) {
162
+ const durationAnalysis = (0, math_utils_1.analyzeIntervals)(durations);
163
+ if (durationAnalysis) {
164
+ const { statistics, allIdentical } = durationAnalysis;
165
+ // All identical = bot
166
+ if (allIdentical) {
167
+ return 0.05;
168
+ }
169
+ // Human tap durations have moderate CV (~0.3-0.5)
170
+ score += (0, math_utils_1.scoreCoefficientOfVariation)(statistics.cv);
171
+ factors++;
172
+ // Ideal duration: 70-120ms
173
+ score += (0, math_utils_1.gaussian)(statistics.mean, 95, 40);
174
+ factors++;
175
+ }
176
+ }
177
+ // 3. Tap movement - Real fingers move slightly (1-5px), bots are often 0px
178
+ const movements = this.events.map(e => e.movement);
179
+ if (movements.length > 0) {
180
+ const avgMovement = movements.reduce((a, b) => a + b, 0) / movements.length;
181
+ // Some movement is natural (1-5px), too much is a swipe, zero is suspicious
182
+ score += (0, math_utils_1.gaussian)(avgMovement, 2, 3);
183
+ factors++;
184
+ }
185
+ // 4. Tap interval timing - Time between taps should vary naturally
186
+ if (this.events.length >= 3) {
187
+ const intervals = [];
188
+ for (let i = 1; i < this.events.length; i++) {
189
+ intervals.push(this.events[i].timestamp - this.events[i - 1].timestamp);
190
+ }
191
+ const intervalAnalysis = (0, math_utils_1.analyzeIntervals)(intervals);
192
+ if (intervalAnalysis && !intervalAnalysis.allIdentical) {
193
+ score += (0, math_utils_1.scoreCoefficientOfVariation)(intervalAnalysis.statistics.cv);
194
+ factors++;
195
+ }
196
+ }
197
+ return factors > 0 ? score / factors : undefined;
198
+ }
199
+ getDebugInfo() {
200
+ return {
201
+ eventCount: this.events.length,
202
+ positions: {
203
+ outside: this.events.filter(e => e.position === 'outside').length,
204
+ deadCenter: this.events.filter(e => e.position === 'dead-center').length,
205
+ overElement: this.events.filter(e => e.position === 'over-element').length,
206
+ },
207
+ durations: this.events.map(e => e.duration),
208
+ movements: this.events.map(e => e.movement),
209
+ inViewport: this.events.filter(e => e.inViewport).length,
210
+ trackedElements: this.touchStartListeners.size,
211
+ };
212
+ }
213
+ }
214
+ exports.TapStrategy = TapStrategy;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Detection Strategy Interface
3
+ * Each strategy is a fully autonomous module responsible for:
4
+ * - Registering its own event listeners
5
+ * - Managing its own state and events
6
+ * - Responding to lifecycle events (start/stop)
7
+ * - Optionally receiving tick updates for polling-based detection
8
+ */
9
+ /**
10
+ * Time series data point
11
+ * Used by strategies that track values over time
12
+ */
13
+ export interface TimeSeriesPoint {
14
+ value: number;
15
+ timestamp: number;
16
+ }
17
+ /**
18
+ * Event notification from strategy to detector
19
+ */
20
+ export interface StrategyEvent {
21
+ strategy: string;
22
+ weight: number;
23
+ timestamp: number;
24
+ }
25
+ /**
26
+ * Base class for detection strategies
27
+ * Handles event callback pattern to avoid repetition
28
+ */
29
+ export declare abstract class BaseStrategy implements DetectionStrategy {
30
+ abstract readonly name: string;
31
+ abstract readonly defaultWeight: number;
32
+ protected eventCallback: ((event: StrategyEvent) => void) | null;
33
+ setEventCallback(callback: (event: StrategyEvent) => void): void;
34
+ protected notifyEvent(weight: number): void;
35
+ abstract start(): void;
36
+ abstract stop(): void;
37
+ abstract reset(): void;
38
+ abstract score(): number | undefined;
39
+ onTick?(timestamp: number): void;
40
+ getDebugInfo?(): any;
41
+ }
42
+ export interface DetectionStrategy {
43
+ /**
44
+ * Unique identifier for this strategy
45
+ */
46
+ readonly name: string;
47
+ /**
48
+ * Default weight for this strategy in overall score calculation
49
+ */
50
+ readonly defaultWeight: number;
51
+ /**
52
+ * Start detection - register event listeners
53
+ */
54
+ start(): void;
55
+ /**
56
+ * Stop detection - remove event listeners, cleanup
57
+ */
58
+ stop(): void;
59
+ /**
60
+ * Reset collected data
61
+ */
62
+ reset(): void;
63
+ /**
64
+ * Calculate score based on collected data
65
+ * @returns Score 0-1, or undefined if insufficient data
66
+ */
67
+ score(): number | undefined;
68
+ /**
69
+ * Optional: Called on regular interval for polling-based detection
70
+ * Use this to sample document/window/navigator state
71
+ * @param timestamp - Current timestamp
72
+ */
73
+ onTick?(timestamp: number): void;
74
+ /**
75
+ * Optional: Get debug information about collected data
76
+ */
77
+ getDebugInfo?(): any;
78
+ /**
79
+ * Optional: Set callback for when events are received
80
+ * Used to track confidence - detector will be notified when strategy receives data
81
+ * @param callback - Function to call when event is received
82
+ */
83
+ setEventCallback?(callback: (event: StrategyEvent) => void): void;
84
+ }
85
+ export interface StrategyConfig {
86
+ strategy: DetectionStrategy;
87
+ weight: number;
88
+ enabled: boolean;
89
+ }
90
+ export interface TickOptions {
91
+ /**
92
+ * Tick interval in milliseconds
93
+ * Default: 1000 (1 second)
94
+ */
95
+ interval?: number;
96
+ /**
97
+ * Whether to start ticking immediately
98
+ * Default: false (start with detector.start())
99
+ */
100
+ autoStart?: boolean;
101
+ /**
102
+ * Automatically pause detection when the tab is hidden
103
+ * Uses the Page Visibility API to stop strategies and tick when document.hidden is true
104
+ * Default: true (recommended for better performance and battery life)
105
+ */
106
+ pauseOnHidden?: boolean;
107
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * Detection Strategy Interface
4
+ * Each strategy is a fully autonomous module responsible for:
5
+ * - Registering its own event listeners
6
+ * - Managing its own state and events
7
+ * - Responding to lifecycle events (start/stop)
8
+ * - Optionally receiving tick updates for polling-based detection
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.BaseStrategy = void 0;
12
+ /**
13
+ * Base class for detection strategies
14
+ * Handles event callback pattern to avoid repetition
15
+ */
16
+ class BaseStrategy {
17
+ constructor() {
18
+ this.eventCallback = null;
19
+ }
20
+ setEventCallback(callback) {
21
+ this.eventCallback = callback;
22
+ }
23
+ notifyEvent(weight) {
24
+ if (this.eventCallback) {
25
+ this.eventCallback({
26
+ strategy: this.name,
27
+ weight,
28
+ timestamp: Date.now()
29
+ });
30
+ }
31
+ }
32
+ }
33
+ exports.BaseStrategy = BaseStrategy;