@axeptio/behavior-detection 1.0.2 → 1.1.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 (59) hide show
  1. package/README.md +126 -12
  2. package/dist/behavior-detection.esm.min.js +1 -1
  3. package/dist/behavior-detection.esm.min.js.map +4 -4
  4. package/dist/behavior-detection.min.js +1 -1
  5. package/dist/behavior-detection.min.js.map +3 -3
  6. package/dist/cjs/index.cjs +2320 -0
  7. package/dist/esm/browser.js +0 -2
  8. package/dist/esm/index.d.ts +1 -1
  9. package/dist/esm/index.js +1 -1
  10. package/dist/esm/strategies/click.d.ts +8 -0
  11. package/dist/esm/strategies/click.js +108 -9
  12. package/dist/esm/strategies/environment.d.ts +39 -0
  13. package/dist/esm/strategies/environment.js +188 -7
  14. package/dist/esm/strategies/index.d.ts +4 -0
  15. package/dist/esm/strategies/index.js +4 -0
  16. package/dist/esm/strategies/mouse.d.ts +53 -1
  17. package/dist/esm/strategies/mouse.js +198 -2
  18. package/dist/esm/strategies/timing.d.ts +64 -0
  19. package/dist/esm/strategies/timing.js +308 -0
  20. package/dist/esm/strategies/visibility.d.ts +64 -0
  21. package/dist/esm/strategies/visibility.js +295 -0
  22. package/dist/index.d.ts +1 -1
  23. package/dist/strategies/click.d.ts +8 -0
  24. package/dist/strategies/environment.d.ts +39 -0
  25. package/dist/strategies/index.d.ts +4 -0
  26. package/dist/strategies/mouse.d.ts +53 -1
  27. package/dist/strategies/timing.d.ts +64 -0
  28. package/dist/strategies/visibility.d.ts +64 -0
  29. package/package.json +16 -15
  30. package/dist/cjs/behavior-detector.d.ts +0 -102
  31. package/dist/cjs/behavior-detector.js +0 -315
  32. package/dist/cjs/browser.d.ts +0 -33
  33. package/dist/cjs/browser.js +0 -226
  34. package/dist/cjs/index.d.ts +0 -38
  35. package/dist/cjs/index.js +0 -55
  36. package/dist/cjs/math-utils.d.ts +0 -84
  37. package/dist/cjs/math-utils.js +0 -141
  38. package/dist/cjs/strategies/click.d.ts +0 -39
  39. package/dist/cjs/strategies/click.js +0 -173
  40. package/dist/cjs/strategies/environment.d.ts +0 -52
  41. package/dist/cjs/strategies/environment.js +0 -148
  42. package/dist/cjs/strategies/index.d.ts +0 -18
  43. package/dist/cjs/strategies/index.js +0 -36
  44. package/dist/cjs/strategies/keyboard.d.ts +0 -43
  45. package/dist/cjs/strategies/keyboard.js +0 -233
  46. package/dist/cjs/strategies/mouse.d.ts +0 -39
  47. package/dist/cjs/strategies/mouse.js +0 -159
  48. package/dist/cjs/strategies/resize.d.ts +0 -21
  49. package/dist/cjs/strategies/resize.js +0 -97
  50. package/dist/cjs/strategies/scroll.d.ts +0 -37
  51. package/dist/cjs/strategies/scroll.js +0 -149
  52. package/dist/cjs/strategies/tap.d.ts +0 -38
  53. package/dist/cjs/strategies/tap.js +0 -214
  54. package/dist/cjs/strategy.d.ts +0 -107
  55. package/dist/cjs/strategy.js +0 -33
  56. package/dist/cjs/types.d.ts +0 -168
  57. package/dist/cjs/types.js +0 -26
  58. package/dist/esm/browser-iife.d.ts +0 -5
  59. package/dist/esm/browser-iife.js +0 -157
@@ -0,0 +1,2320 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // dist/esm/index.js
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BehaviorDetector: () => BehaviorDetector,
24
+ Click: () => ClickStrategy,
25
+ ClickStrategy: () => ClickStrategy,
26
+ DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
27
+ Environment: () => EnvironmentStrategy,
28
+ EnvironmentStrategy: () => EnvironmentStrategy,
29
+ Keyboard: () => KeyboardStrategy,
30
+ KeyboardStrategy: () => KeyboardStrategy,
31
+ Mouse: () => MouseStrategy,
32
+ MouseStrategy: () => MouseStrategy,
33
+ Resize: () => ResizeStrategy,
34
+ ResizeStrategy: () => ResizeStrategy,
35
+ Scroll: () => ScrollStrategy,
36
+ ScrollStrategy: () => ScrollStrategy,
37
+ Tap: () => TapStrategy,
38
+ TapStrategy: () => TapStrategy,
39
+ Timing: () => TimingStrategy,
40
+ TimingStrategy: () => TimingStrategy,
41
+ Visibility: () => VisibilityStrategy,
42
+ VisibilityStrategy: () => VisibilityStrategy
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // dist/esm/behavior-detector.js
47
+ var BehaviorDetector = class {
48
+ constructor(tickOptions) {
49
+ this.strategies = /* @__PURE__ */ new Map();
50
+ this.isTracking = false;
51
+ this.isPausedByVisibility = false;
52
+ this.tickInterval = null;
53
+ this.tickIntervalMs = 1e3;
54
+ this.pauseOnHidden = true;
55
+ this.visibilityChangeHandler = null;
56
+ this.confidenceScore = 0;
57
+ this.CONFIDENCE_TARGET = 1;
58
+ this.CONFIDENCE_DECAY = 0.95;
59
+ if (tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.interval) {
60
+ this.tickIntervalMs = tickOptions.interval;
61
+ }
62
+ if ((tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.pauseOnHidden) !== void 0) {
63
+ this.pauseOnHidden = tickOptions.pauseOnHidden;
64
+ }
65
+ }
66
+ /**
67
+ * Add a detection strategy
68
+ */
69
+ addStrategy(strategy, weight) {
70
+ const config = {
71
+ strategy,
72
+ weight: weight !== null && weight !== void 0 ? weight : strategy.defaultWeight,
73
+ enabled: true
74
+ };
75
+ this.strategies.set(strategy.name, config);
76
+ if (strategy.setEventCallback) {
77
+ strategy.setEventCallback((event) => {
78
+ this.onStrategyEvent(event, config.weight);
79
+ });
80
+ }
81
+ if (this.isTracking) {
82
+ strategy.start();
83
+ }
84
+ return this;
85
+ }
86
+ /**
87
+ * Handle event from strategy - update confidence
88
+ */
89
+ onStrategyEvent(event, strategyWeight) {
90
+ this.confidenceScore *= this.CONFIDENCE_DECAY;
91
+ const contribution = event.weight * strategyWeight;
92
+ this.confidenceScore = Math.min(this.CONFIDENCE_TARGET, this.confidenceScore + contribution);
93
+ }
94
+ /**
95
+ * Remove a detection strategy
96
+ */
97
+ removeStrategy(name) {
98
+ const config = this.strategies.get(name);
99
+ if (config) {
100
+ if (this.isTracking) {
101
+ config.strategy.stop();
102
+ }
103
+ this.strategies.delete(name);
104
+ }
105
+ return this;
106
+ }
107
+ /**
108
+ * Enable/disable a strategy
109
+ */
110
+ setStrategyEnabled(name, enabled) {
111
+ const config = this.strategies.get(name);
112
+ if (config) {
113
+ config.enabled = enabled;
114
+ if (!enabled && this.isTracking) {
115
+ config.strategy.stop();
116
+ }
117
+ if (enabled && this.isTracking) {
118
+ config.strategy.start();
119
+ }
120
+ }
121
+ return this;
122
+ }
123
+ /**
124
+ * Get all registered strategies
125
+ */
126
+ getStrategies() {
127
+ return this.strategies;
128
+ }
129
+ /**
130
+ * Start tracking
131
+ */
132
+ start() {
133
+ if (this.isTracking)
134
+ return;
135
+ this.isTracking = true;
136
+ if (this.pauseOnHidden && typeof document !== "undefined") {
137
+ this.visibilityChangeHandler = this.handleVisibilityChange.bind(this);
138
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
139
+ if (document.hidden) {
140
+ this.isPausedByVisibility = true;
141
+ return;
142
+ }
143
+ }
144
+ for (const [_, config] of this.strategies) {
145
+ if (config.enabled) {
146
+ config.strategy.start();
147
+ }
148
+ }
149
+ this.startTick();
150
+ }
151
+ /**
152
+ * Stop tracking
153
+ */
154
+ stop() {
155
+ if (!this.isTracking)
156
+ return;
157
+ this.isTracking = false;
158
+ this.isPausedByVisibility = false;
159
+ if (this.visibilityChangeHandler && typeof document !== "undefined") {
160
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
161
+ this.visibilityChangeHandler = null;
162
+ }
163
+ for (const [_, config] of this.strategies) {
164
+ config.strategy.stop();
165
+ }
166
+ this.stopTick();
167
+ }
168
+ /**
169
+ * Reset all data
170
+ */
171
+ reset() {
172
+ this.confidenceScore = 0;
173
+ for (const [_, config] of this.strategies) {
174
+ config.strategy.reset();
175
+ }
176
+ }
177
+ /**
178
+ * Calculate human likelihood score
179
+ */
180
+ async score(options = {}) {
181
+ const breakdown = this.calculateStrategyScore();
182
+ const result = {
183
+ score: breakdown.overall
184
+ };
185
+ if (options.breakdown) {
186
+ result.breakdown = breakdown;
187
+ }
188
+ return result;
189
+ }
190
+ /**
191
+ * Check if currently tracking
192
+ */
193
+ isActive() {
194
+ return this.isTracking;
195
+ }
196
+ /**
197
+ * Check if currently paused due to tab visibility
198
+ */
199
+ isPaused() {
200
+ return this.isPausedByVisibility;
201
+ }
202
+ /**
203
+ * Get event count from all strategies
204
+ */
205
+ getEventCount() {
206
+ var _a, _b;
207
+ const counts = {};
208
+ for (const [name, config] of this.strategies) {
209
+ const debug = (_b = (_a = config.strategy).getDebugInfo) === null || _b === void 0 ? void 0 : _b.call(_a);
210
+ if ((debug === null || debug === void 0 ? void 0 : debug.eventCount) !== void 0) {
211
+ counts[name] = debug.eventCount;
212
+ }
213
+ }
214
+ return counts;
215
+ }
216
+ /**
217
+ * Get debug info from all strategies
218
+ */
219
+ getStrategyDebugInfo() {
220
+ const debug = {};
221
+ for (const [name, config] of this.strategies) {
222
+ if (config.strategy.getDebugInfo) {
223
+ debug[name] = config.strategy.getDebugInfo();
224
+ }
225
+ }
226
+ return debug;
227
+ }
228
+ /**
229
+ * Get current confidence score (0-1)
230
+ * Represents how much interaction data we've collected
231
+ * Higher confidence = more reliable classification
232
+ */
233
+ getConfidence() {
234
+ return Math.min(1, this.confidenceScore);
235
+ }
236
+ /**
237
+ * Check if confidence is above threshold for reliable classification
238
+ * @param threshold - Minimum confidence (0-1), default 0.3
239
+ */
240
+ hasConfidentData(threshold = 0.3) {
241
+ return this.getConfidence() >= threshold;
242
+ }
243
+ /**
244
+ * Cleanup resources
245
+ */
246
+ destroy() {
247
+ this.stop();
248
+ this.strategies.clear();
249
+ }
250
+ /**
251
+ * Handle visibility change events
252
+ */
253
+ handleVisibilityChange() {
254
+ if (!this.isTracking)
255
+ return;
256
+ if (document.hidden) {
257
+ if (!this.isPausedByVisibility) {
258
+ this.isPausedByVisibility = true;
259
+ for (const [_, config] of this.strategies) {
260
+ if (config.enabled) {
261
+ config.strategy.stop();
262
+ }
263
+ }
264
+ this.stopTick();
265
+ }
266
+ } else {
267
+ if (this.isPausedByVisibility) {
268
+ this.isPausedByVisibility = false;
269
+ for (const [_, config] of this.strategies) {
270
+ if (config.enabled) {
271
+ config.strategy.start();
272
+ }
273
+ }
274
+ this.startTick();
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * Start tick mechanism for strategies
280
+ */
281
+ startTick() {
282
+ if (this.tickInterval !== null)
283
+ return;
284
+ this.tickInterval = window.setInterval(() => {
285
+ const now = Date.now();
286
+ for (const [_, config] of this.strategies) {
287
+ if (config.enabled && config.strategy.onTick) {
288
+ config.strategy.onTick(now);
289
+ }
290
+ }
291
+ }, this.tickIntervalMs);
292
+ }
293
+ /**
294
+ * Stop tick mechanism
295
+ */
296
+ stopTick() {
297
+ if (this.tickInterval !== null) {
298
+ clearInterval(this.tickInterval);
299
+ this.tickInterval = null;
300
+ }
301
+ }
302
+ /**
303
+ * Calculate score using strategies
304
+ */
305
+ calculateStrategyScore() {
306
+ const factors = {};
307
+ const weights = {};
308
+ let totalWeight = 0;
309
+ let weightedSum = 0;
310
+ for (const [name, config] of this.strategies) {
311
+ if (!config.enabled)
312
+ continue;
313
+ const score = config.strategy.score();
314
+ factors[name] = score;
315
+ weights[name] = config.weight;
316
+ if (score !== void 0 && score !== null) {
317
+ totalWeight += config.weight;
318
+ weightedSum += score * config.weight;
319
+ }
320
+ }
321
+ const overall = totalWeight > 0 ? weightedSum / totalWeight : 0.5;
322
+ return {
323
+ overall: Math.max(0, Math.min(1, overall)),
324
+ factors,
325
+ weights
326
+ };
327
+ }
328
+ };
329
+
330
+ // dist/esm/strategy.js
331
+ var BaseStrategy = class {
332
+ constructor() {
333
+ this.eventCallback = null;
334
+ }
335
+ setEventCallback(callback) {
336
+ this.eventCallback = callback;
337
+ }
338
+ notifyEvent(weight) {
339
+ if (this.eventCallback) {
340
+ this.eventCallback({
341
+ strategy: this.name,
342
+ weight,
343
+ timestamp: Date.now()
344
+ });
345
+ }
346
+ }
347
+ };
348
+
349
+ // dist/esm/math-utils.js
350
+ function sigmoid(x, midpoint = 0, steepness = 1) {
351
+ return 1 / (1 + Math.exp(-steepness * (x - midpoint)));
352
+ }
353
+ function inverseSigmoid(x, midpoint = 0, steepness = 1) {
354
+ return 1 - sigmoid(x, midpoint, steepness);
355
+ }
356
+ function gaussian(x, ideal = 0, width = 1) {
357
+ const exponent = -Math.pow(x - ideal, 2) / (2 * width * width);
358
+ return Math.exp(exponent);
359
+ }
360
+ function calculateStatistics(values) {
361
+ if (values.length === 0) {
362
+ return { mean: 0, variance: 0, stdDev: 0, cv: 0 };
363
+ }
364
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
365
+ const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
366
+ const stdDev = Math.sqrt(variance);
367
+ const cv = mean > 0 ? stdDev / mean : 0;
368
+ return { mean, variance, stdDev, cv };
369
+ }
370
+ function analyzeIntervals(intervals) {
371
+ if (intervals.length < 2)
372
+ return void 0;
373
+ const uniqueCount = new Set(intervals.map((i) => Math.round(i / 10))).size;
374
+ const allIdentical = uniqueCount === 1;
375
+ const statistics = calculateStatistics(intervals);
376
+ return {
377
+ statistics,
378
+ uniqueCount,
379
+ allIdentical
380
+ };
381
+ }
382
+ function scoreCoefficientOfVariation(cv) {
383
+ if (cv < 0.05)
384
+ return 0.1;
385
+ if (cv > 2)
386
+ return 0.3;
387
+ return gaussian(cv, 0.45, 0.35);
388
+ }
389
+
390
+ // dist/esm/strategies/mouse.js
391
+ var MouseStrategy = class extends BaseStrategy {
392
+ constructor(options) {
393
+ super();
394
+ this.name = "mouse";
395
+ this.defaultWeight = 0.3;
396
+ this.distanceSeries = [];
397
+ this.angleSeries = [];
398
+ this.lastPosition = null;
399
+ this.lastAngle = 0;
400
+ this.cumulativeAngle = 0;
401
+ this.rollingWindowMs = 5e3;
402
+ this.listener = null;
403
+ this.leaveListener = null;
404
+ this.enterListener = null;
405
+ this.isActive = false;
406
+ this.screenDiagonal = 1;
407
+ this.entryPoints = [];
408
+ this.microMovements = [];
409
+ this.STILLNESS_WINDOW = 500;
410
+ if ((options === null || options === void 0 ? void 0 : options.rollingWindow) !== void 0)
411
+ this.rollingWindowMs = options.rollingWindow;
412
+ }
413
+ start() {
414
+ if (this.isActive)
415
+ return;
416
+ this.isActive = true;
417
+ const width = window.innerWidth;
418
+ const height = window.innerHeight;
419
+ this.screenDiagonal = Math.sqrt(width * width + height * height);
420
+ this.listener = (e) => {
421
+ const mouseEvent = e;
422
+ const now = Date.now();
423
+ const currentPos = { x: mouseEvent.clientX, y: mouseEvent.clientY, timestamp: now };
424
+ if (this.lastPosition) {
425
+ const dx = currentPos.x - this.lastPosition.x;
426
+ const dy = currentPos.y - this.lastPosition.y;
427
+ const pixelDistance = Math.sqrt(dx * dx + dy * dy);
428
+ if (pixelDistance >= 1 && pixelDistance <= 5) {
429
+ this.microMovements.push({ dx, dy, timestamp: now });
430
+ const cutoffMicro = now - this.STILLNESS_WINDOW;
431
+ while (this.microMovements.length > 0 && this.microMovements[0].timestamp < cutoffMicro) {
432
+ this.microMovements.shift();
433
+ }
434
+ }
435
+ const normalizedDistance = pixelDistance / this.screenDiagonal;
436
+ if (normalizedDistance > 1e-3) {
437
+ const rawAngle = Math.atan2(dy, dx);
438
+ let angleDiff = rawAngle - this.lastAngle;
439
+ while (angleDiff > Math.PI)
440
+ angleDiff -= 2 * Math.PI;
441
+ while (angleDiff < -Math.PI)
442
+ angleDiff += 2 * Math.PI;
443
+ this.cumulativeAngle += angleDiff;
444
+ this.lastAngle = rawAngle;
445
+ this.distanceSeries.push({ value: normalizedDistance, timestamp: now });
446
+ this.angleSeries.push({ value: this.cumulativeAngle, timestamp: now });
447
+ this.notifyEvent(Math.min(1, normalizedDistance * 100));
448
+ }
449
+ const cutoff = now - this.rollingWindowMs;
450
+ while (this.distanceSeries.length > 0 && this.distanceSeries[0].timestamp < cutoff) {
451
+ this.distanceSeries.shift();
452
+ this.angleSeries.shift();
453
+ }
454
+ }
455
+ this.lastPosition = currentPos;
456
+ };
457
+ document.addEventListener("mousemove", this.listener, { passive: true });
458
+ this.enterListener = (e) => {
459
+ const mouseEvent = e;
460
+ const now = Date.now();
461
+ const x = mouseEvent.clientX;
462
+ const y = mouseEvent.clientY;
463
+ const width2 = window.innerWidth;
464
+ const height2 = window.innerHeight;
465
+ const distTop = y;
466
+ const distBottom = height2 - y;
467
+ const distLeft = x;
468
+ const distRight = width2 - x;
469
+ const minEdgeDist = Math.min(distTop, distBottom, distLeft, distRight);
470
+ let entryEdge;
471
+ const edgeThreshold = 50;
472
+ if (minEdgeDist < edgeThreshold) {
473
+ if (distTop <= minEdgeDist)
474
+ entryEdge = "top";
475
+ else if (distBottom <= minEdgeDist)
476
+ entryEdge = "bottom";
477
+ else if (distLeft <= minEdgeDist)
478
+ entryEdge = "left";
479
+ else
480
+ entryEdge = "right";
481
+ } else if (x > width2 * 0.35 && x < width2 * 0.65 && y > height2 * 0.35 && y < height2 * 0.65) {
482
+ entryEdge = "center";
483
+ } else if (distTop < edgeThreshold * 2 && distLeft < edgeThreshold * 2 || distTop < edgeThreshold * 2 && distRight < edgeThreshold * 2 || distBottom < edgeThreshold * 2 && distLeft < edgeThreshold * 2 || distBottom < edgeThreshold * 2 && distRight < edgeThreshold * 2) {
484
+ entryEdge = "corner";
485
+ } else {
486
+ if (distTop < distBottom && distTop < distLeft && distTop < distRight)
487
+ entryEdge = "top";
488
+ else if (distBottom < distLeft && distBottom < distRight)
489
+ entryEdge = "bottom";
490
+ else if (distLeft < distRight)
491
+ entryEdge = "left";
492
+ else
493
+ entryEdge = "right";
494
+ }
495
+ this.entryPoints.push({
496
+ x,
497
+ y,
498
+ timestamp: now,
499
+ edgeDistance: minEdgeDist,
500
+ entryEdge
501
+ });
502
+ if (this.entryPoints.length > 20) {
503
+ this.entryPoints.shift();
504
+ }
505
+ };
506
+ document.addEventListener("mouseenter", this.enterListener, { passive: true });
507
+ this.leaveListener = () => {
508
+ this.distanceSeries = [];
509
+ this.angleSeries = [];
510
+ this.lastPosition = null;
511
+ this.lastAngle = 0;
512
+ this.cumulativeAngle = 0;
513
+ this.microMovements = [];
514
+ };
515
+ document.addEventListener("mouseleave", this.leaveListener, { passive: true });
516
+ }
517
+ stop() {
518
+ if (!this.isActive)
519
+ return;
520
+ this.isActive = false;
521
+ if (this.listener) {
522
+ document.removeEventListener("mousemove", this.listener);
523
+ this.listener = null;
524
+ }
525
+ if (this.leaveListener) {
526
+ document.removeEventListener("mouseleave", this.leaveListener);
527
+ this.leaveListener = null;
528
+ }
529
+ if (this.enterListener) {
530
+ document.removeEventListener("mouseenter", this.enterListener);
531
+ this.enterListener = null;
532
+ }
533
+ }
534
+ reset() {
535
+ this.distanceSeries = [];
536
+ this.angleSeries = [];
537
+ this.lastPosition = null;
538
+ this.lastAngle = 0;
539
+ this.cumulativeAngle = 0;
540
+ this.entryPoints = [];
541
+ this.microMovements = [];
542
+ }
543
+ score() {
544
+ if (this.distanceSeries.length < 10)
545
+ return void 0;
546
+ return this.detectMousePatterns();
547
+ }
548
+ /**
549
+ * Mouse-specific pattern detection
550
+ * Detects bot-like patterns: constant velocity, linear paths, suspicious entry points
551
+ */
552
+ detectMousePatterns() {
553
+ if (this.distanceSeries.length < 10)
554
+ return void 0;
555
+ let score = 0;
556
+ let factors = 0;
557
+ const distances = this.distanceSeries.map((p) => p.value);
558
+ if (distances.length >= 3) {
559
+ const stats = calculateStatistics(distances);
560
+ score += gaussian(stats.cv, 0.9, 0.35);
561
+ factors++;
562
+ }
563
+ const angles = this.angleSeries.map((p) => p.value);
564
+ if (angles.length >= 3) {
565
+ const angleChanges = [];
566
+ for (let i = 1; i < angles.length; i++) {
567
+ angleChanges.push(Math.abs(angles[i] - angles[i - 1]));
568
+ }
569
+ const avgChange = angleChanges.reduce((a, b) => a + b, 0) / angleChanges.length;
570
+ score += gaussian(avgChange, 0.15, 0.12);
571
+ factors++;
572
+ }
573
+ const entryScore = this.scoreEntryPoints();
574
+ if (entryScore !== void 0) {
575
+ score += entryScore;
576
+ factors++;
577
+ }
578
+ const microScore = this.scoreMicroMovements();
579
+ if (microScore !== void 0) {
580
+ score += microScore;
581
+ factors++;
582
+ }
583
+ return factors > 0 ? score / factors : void 0;
584
+ }
585
+ /**
586
+ * Score based on viewport entry points
587
+ * Humans enter from edges with momentum; bots often start at (0,0) or center
588
+ */
589
+ scoreEntryPoints() {
590
+ if (this.entryPoints.length < 2)
591
+ return void 0;
592
+ let suspiciousCount = 0;
593
+ let centerCount = 0;
594
+ let cornerCount = 0;
595
+ let originCount = 0;
596
+ for (const entry of this.entryPoints) {
597
+ if (entry.x < 5 && entry.y < 5) {
598
+ originCount++;
599
+ suspiciousCount++;
600
+ } else if (entry.entryEdge === "center") {
601
+ centerCount++;
602
+ suspiciousCount++;
603
+ } else if (entry.entryEdge === "corner") {
604
+ cornerCount++;
605
+ if (entry.edgeDistance < 10)
606
+ suspiciousCount++;
607
+ }
608
+ }
609
+ const total = this.entryPoints.length;
610
+ const suspiciousRatio = suspiciousCount / total;
611
+ if (originCount >= 2 || originCount / total >= 0.5)
612
+ return 0.1;
613
+ if (centerCount / total >= 0.5)
614
+ return 0.2;
615
+ if (suspiciousRatio >= 0.7)
616
+ return 0.3;
617
+ if (suspiciousRatio >= 0.5)
618
+ return 0.5;
619
+ if (suspiciousRatio >= 0.3)
620
+ return 0.7;
621
+ return 1;
622
+ }
623
+ /**
624
+ * Score based on micro-movements (tremor detection)
625
+ * Humans have natural hand tremor causing 1-5px jitter
626
+ * Bots have perfect stillness or no micro-movements
627
+ */
628
+ scoreMicroMovements() {
629
+ if (this.distanceSeries.length < 20)
630
+ return void 0;
631
+ const microCount = this.microMovements.length;
632
+ const oldestEvent = this.distanceSeries[0];
633
+ const newestEvent = this.distanceSeries[this.distanceSeries.length - 1];
634
+ const durationMs = newestEvent.timestamp - oldestEvent.timestamp;
635
+ if (durationMs < 1e3)
636
+ return void 0;
637
+ const microPerSecond = microCount / durationMs * 1e3;
638
+ if (microCount === 0)
639
+ return 0.3;
640
+ if (microPerSecond < 1)
641
+ return 0.5;
642
+ if (microPerSecond < 5)
643
+ return 0.7;
644
+ if (microPerSecond >= 5 && microPerSecond <= 30)
645
+ return 1;
646
+ return 0.8;
647
+ }
648
+ /**
649
+ * Get micro-movement count for external use (e.g., by ClickStrategy)
650
+ */
651
+ getMicroMovementCount() {
652
+ return this.microMovements.length;
653
+ }
654
+ /**
655
+ * Get last position for external use
656
+ */
657
+ getLastPosition() {
658
+ return this.lastPosition ? { x: this.lastPosition.x, y: this.lastPosition.y } : null;
659
+ }
660
+ getDebugInfo() {
661
+ return {
662
+ eventCount: this.distanceSeries.length,
663
+ rollingWindow: this.rollingWindowMs,
664
+ isActive: this.isActive,
665
+ distanceSeries: this.distanceSeries,
666
+ angleSeries: this.angleSeries,
667
+ entryPoints: this.entryPoints,
668
+ microMovementCount: this.microMovements.length,
669
+ lastPosition: this.lastPosition
670
+ };
671
+ }
672
+ };
673
+
674
+ // dist/esm/strategies/scroll.js
675
+ var ScrollStrategy = class extends BaseStrategy {
676
+ constructor(options) {
677
+ super();
678
+ this.name = "scroll";
679
+ this.defaultWeight = 0.15;
680
+ this.distanceSeries = [];
681
+ this.velocitySeries = [];
682
+ this.lastScrollY = null;
683
+ this.lastTimestamp = 0;
684
+ this.rollingWindowMs = 5e3;
685
+ this.documentHeight = 1;
686
+ this.listener = null;
687
+ this.isActive = false;
688
+ if ((options === null || options === void 0 ? void 0 : options.rollingWindow) !== void 0)
689
+ this.rollingWindowMs = options.rollingWindow;
690
+ }
691
+ start() {
692
+ if (this.isActive)
693
+ return;
694
+ this.isActive = true;
695
+ this.documentHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, 1);
696
+ this.listener = (_e) => {
697
+ const scrollY = window.scrollY;
698
+ const now = Date.now();
699
+ if (this.lastScrollY === null) {
700
+ this.lastScrollY = scrollY;
701
+ this.lastTimestamp = now;
702
+ return;
703
+ }
704
+ const pixelDelta = scrollY - this.lastScrollY;
705
+ const deltaTime = now - this.lastTimestamp;
706
+ if (pixelDelta === 0) {
707
+ return;
708
+ }
709
+ const normalizedDistance = Math.abs(pixelDelta) / this.documentHeight;
710
+ if (deltaTime > 0) {
711
+ const velocity = normalizedDistance / deltaTime;
712
+ this.distanceSeries.push({ value: normalizedDistance, timestamp: now });
713
+ this.velocitySeries.push({ value: velocity, timestamp: now });
714
+ this.notifyEvent(Math.min(1, normalizedDistance * 10));
715
+ const cutoff = now - this.rollingWindowMs;
716
+ while (this.distanceSeries.length > 0 && this.distanceSeries[0].timestamp < cutoff) {
717
+ this.distanceSeries.shift();
718
+ this.velocitySeries.shift();
719
+ }
720
+ }
721
+ this.lastScrollY = scrollY;
722
+ this.lastTimestamp = now;
723
+ };
724
+ window.addEventListener("scroll", this.listener, { passive: true });
725
+ }
726
+ stop() {
727
+ if (!this.isActive)
728
+ return;
729
+ this.isActive = false;
730
+ if (this.listener) {
731
+ window.removeEventListener("scroll", this.listener);
732
+ this.listener = null;
733
+ }
734
+ }
735
+ reset() {
736
+ this.distanceSeries = [];
737
+ this.velocitySeries = [];
738
+ this.lastScrollY = null;
739
+ this.lastTimestamp = 0;
740
+ }
741
+ score() {
742
+ if (this.distanceSeries.length < 2)
743
+ return void 0;
744
+ return this.detectScrollPatterns();
745
+ }
746
+ /**
747
+ * Scroll-specific pattern detection
748
+ * Detects bot-like patterns: constant deltas, instant jumps, too smooth
749
+ */
750
+ detectScrollPatterns() {
751
+ if (this.distanceSeries.length < 2)
752
+ return void 0;
753
+ let score = 0;
754
+ let factors = 0;
755
+ const distances = this.distanceSeries.map((p) => p.value);
756
+ if (distances.length >= 2) {
757
+ const uniqueDistances = new Set(distances.map((d) => Math.round(d * 1e3))).size;
758
+ if (uniqueDistances === 1) {
759
+ return 0.05;
760
+ }
761
+ }
762
+ if (distances.length >= 2) {
763
+ const stats = calculateStatistics(distances);
764
+ score += gaussian(stats.cv, 1, 0.4);
765
+ factors++;
766
+ }
767
+ const velocities = this.velocitySeries.map((p) => p.value);
768
+ if (velocities.length >= 2) {
769
+ const stats = calculateStatistics(velocities);
770
+ score += gaussian(stats.cv, 1.2, 0.5);
771
+ factors++;
772
+ }
773
+ const instantJumps = distances.filter((d) => d > 0.1).length;
774
+ if (distances.length > 0) {
775
+ const jumpRatio = instantJumps / distances.length;
776
+ score += inverseSigmoid(jumpRatio, 0.3, 15);
777
+ factors++;
778
+ }
779
+ return factors > 0 ? score / factors : void 0;
780
+ }
781
+ getDebugInfo() {
782
+ return {
783
+ eventCount: this.distanceSeries.length,
784
+ rollingWindow: this.rollingWindowMs,
785
+ isActive: this.isActive,
786
+ distanceSeries: this.distanceSeries,
787
+ velocitySeries: this.velocitySeries
788
+ };
789
+ }
790
+ };
791
+
792
+ // dist/esm/strategies/click.js
793
+ var ClickStrategy = class extends BaseStrategy {
794
+ constructor(options) {
795
+ super();
796
+ this.name = "click";
797
+ this.defaultWeight = 0.3;
798
+ this.events = [];
799
+ this.targetSelectors = ["button", "a", 'input[type="submit"]', '[role="button"]'];
800
+ this.lastMousePosition = null;
801
+ this.mouseListener = null;
802
+ this.clickListeners = /* @__PURE__ */ new Map();
803
+ this.isActive = false;
804
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
805
+ this.targetSelectors = options.targetSelectors;
806
+ }
807
+ }
808
+ start() {
809
+ if (this.isActive)
810
+ return;
811
+ this.isActive = true;
812
+ this.mouseListener = (e) => {
813
+ const mouseEvent = e;
814
+ this.lastMousePosition = {
815
+ x: mouseEvent.clientX,
816
+ y: mouseEvent.clientY,
817
+ timestamp: Date.now()
818
+ };
819
+ };
820
+ document.addEventListener("mousemove", this.mouseListener, { passive: true });
821
+ this.attachClickListeners();
822
+ }
823
+ /**
824
+ * Add a new target selector at runtime
825
+ */
826
+ addTarget(selector) {
827
+ if (!this.targetSelectors.includes(selector)) {
828
+ this.targetSelectors.push(selector);
829
+ if (this.isActive) {
830
+ this.attachClickListenersForSelector(selector);
831
+ }
832
+ }
833
+ }
834
+ attachClickListeners() {
835
+ this.targetSelectors.forEach((selector) => {
836
+ this.attachClickListenersForSelector(selector);
837
+ });
838
+ }
839
+ attachClickListenersForSelector(selector) {
840
+ const elements = document.querySelectorAll(selector);
841
+ elements.forEach((element) => {
842
+ if (this.clickListeners.has(element))
843
+ return;
844
+ const listener = (e) => {
845
+ var _a, _b;
846
+ const clickEvent = e;
847
+ const now = Date.now();
848
+ const rect = element.getBoundingClientRect();
849
+ const inViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
850
+ let mouseClickDelta = 0;
851
+ let timeSinceMouseMove = -1;
852
+ if (this.lastMousePosition) {
853
+ const dx = clickEvent.clientX - this.lastMousePosition.x;
854
+ const dy = clickEvent.clientY - this.lastMousePosition.y;
855
+ mouseClickDelta = Math.sqrt(dx * dx + dy * dy);
856
+ timeSinceMouseMove = now - this.lastMousePosition.timestamp;
857
+ }
858
+ let position;
859
+ if (!this.lastMousePosition) {
860
+ position = "no-mouse-data";
861
+ } else {
862
+ const mx = this.lastMousePosition.x;
863
+ const my = this.lastMousePosition.y;
864
+ const overElement = mx >= rect.left && mx <= rect.right && my >= rect.top && my <= rect.bottom;
865
+ if (!overElement) {
866
+ position = "outside";
867
+ } else {
868
+ const centerX = rect.left + rect.width / 2;
869
+ const centerY = rect.top + rect.height / 2;
870
+ const distanceFromCenter = Math.sqrt((mx - centerX) ** 2 + (my - centerY) ** 2);
871
+ if (distanceFromCenter < 2) {
872
+ position = "dead-center";
873
+ } else {
874
+ position = "over-element";
875
+ }
876
+ }
877
+ }
878
+ this.events.push({
879
+ clickX: clickEvent.clientX,
880
+ clickY: clickEvent.clientY,
881
+ mouseX: (_a = this.lastMousePosition) === null || _a === void 0 ? void 0 : _a.x,
882
+ mouseY: (_b = this.lastMousePosition) === null || _b === void 0 ? void 0 : _b.y,
883
+ rect,
884
+ inViewport,
885
+ position,
886
+ timestamp: now,
887
+ mouseClickDelta,
888
+ timeSinceMouseMove,
889
+ isTrusted: clickEvent.isTrusted
890
+ });
891
+ this.notifyEvent(1);
892
+ };
893
+ element.addEventListener("click", listener);
894
+ this.clickListeners.set(element, listener);
895
+ });
896
+ }
897
+ stop() {
898
+ if (!this.isActive)
899
+ return;
900
+ this.isActive = false;
901
+ if (this.mouseListener) {
902
+ document.removeEventListener("mousemove", this.mouseListener);
903
+ this.mouseListener = null;
904
+ }
905
+ this.clickListeners.forEach((listener, element) => {
906
+ element.removeEventListener("click", listener);
907
+ });
908
+ this.clickListeners.clear();
909
+ }
910
+ reset() {
911
+ this.events = [];
912
+ this.lastMousePosition = null;
913
+ }
914
+ score() {
915
+ const eventCount = this.events.length;
916
+ if (eventCount === 0)
917
+ return void 0;
918
+ let positionScore = 0;
919
+ let clicksWithDelta = 0;
920
+ let mismatchCount = 0;
921
+ let clicksWithTiming = 0;
922
+ let suspiciousTimingCount = 0;
923
+ let untrustedCount = 0;
924
+ for (const click of this.events) {
925
+ switch (click.position) {
926
+ case "no-mouse-data":
927
+ positionScore += 0;
928
+ break;
929
+ case "outside":
930
+ positionScore += 0;
931
+ break;
932
+ case "dead-center":
933
+ positionScore += 0.5;
934
+ break;
935
+ case "over-element":
936
+ positionScore += 1;
937
+ break;
938
+ }
939
+ if (click.mouseClickDelta >= 0) {
940
+ clicksWithDelta++;
941
+ if (click.mouseClickDelta > 5)
942
+ mismatchCount++;
943
+ }
944
+ if (click.timeSinceMouseMove >= 0) {
945
+ clicksWithTiming++;
946
+ if (click.timeSinceMouseMove < 10 || click.timeSinceMouseMove > 2e3) {
947
+ suspiciousTimingCount++;
948
+ }
949
+ }
950
+ if (!click.isTrusted)
951
+ untrustedCount++;
952
+ }
953
+ let score = 0;
954
+ let factors = 0;
955
+ score += positionScore / eventCount;
956
+ factors++;
957
+ if (clicksWithDelta >= 2) {
958
+ const mismatchRatio = mismatchCount / clicksWithDelta;
959
+ let deltaScore;
960
+ if (mismatchRatio >= 0.8)
961
+ deltaScore = 0.1;
962
+ else if (mismatchRatio >= 0.5)
963
+ deltaScore = 0.3;
964
+ else if (mismatchRatio >= 0.3)
965
+ deltaScore = 0.5;
966
+ else if (mismatchRatio > 0)
967
+ deltaScore = 0.7;
968
+ else
969
+ deltaScore = 1;
970
+ score += deltaScore;
971
+ factors++;
972
+ }
973
+ if (clicksWithTiming >= 2) {
974
+ const suspiciousRatio = suspiciousTimingCount / clicksWithTiming;
975
+ let timingScore;
976
+ if (suspiciousRatio >= 0.8)
977
+ timingScore = 0.2;
978
+ else if (suspiciousRatio >= 0.5)
979
+ timingScore = 0.4;
980
+ else if (suspiciousRatio >= 0.3)
981
+ timingScore = 0.6;
982
+ else if (suspiciousRatio > 0)
983
+ timingScore = 0.8;
984
+ else
985
+ timingScore = 1;
986
+ score += timingScore;
987
+ factors++;
988
+ }
989
+ if (eventCount >= 2) {
990
+ const untrustedRatio = untrustedCount / eventCount;
991
+ let trustedScore;
992
+ if (untrustedRatio >= 0.5)
993
+ trustedScore = 0.1;
994
+ else if (untrustedRatio > 0)
995
+ trustedScore = 0.3;
996
+ else
997
+ trustedScore = 1;
998
+ score += trustedScore;
999
+ factors++;
1000
+ }
1001
+ return factors > 0 ? score / factors : void 0;
1002
+ }
1003
+ getDebugInfo() {
1004
+ return {
1005
+ eventCount: this.events.length,
1006
+ positions: {
1007
+ noMouseData: this.events.filter((e) => e.position === "no-mouse-data").length,
1008
+ outside: this.events.filter((e) => e.position === "outside").length,
1009
+ deadCenter: this.events.filter((e) => e.position === "dead-center").length,
1010
+ overElement: this.events.filter((e) => e.position === "over-element").length
1011
+ },
1012
+ inViewport: this.events.filter((e) => e.inViewport).length,
1013
+ trackedElements: this.clickListeners.size,
1014
+ mouseClickDeltas: this.events.map((e) => e.mouseClickDelta),
1015
+ timeSinceMouseMoves: this.events.map((e) => e.timeSinceMouseMove),
1016
+ untrustedClicks: this.events.filter((e) => !e.isTrusted).length
1017
+ };
1018
+ }
1019
+ };
1020
+
1021
+ // dist/esm/strategies/tap.js
1022
+ var TapStrategy = class extends BaseStrategy {
1023
+ constructor(options) {
1024
+ super();
1025
+ this.name = "tap";
1026
+ this.defaultWeight = 0.35;
1027
+ this.events = [];
1028
+ this.targetSelectors = ["button", "a", 'input[type="submit"]', '[role="button"]'];
1029
+ this.touchStartListeners = /* @__PURE__ */ new Map();
1030
+ this.touchEndListeners = /* @__PURE__ */ new Map();
1031
+ this.activeTouches = /* @__PURE__ */ new Map();
1032
+ this.isActive = false;
1033
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
1034
+ this.targetSelectors = options.targetSelectors;
1035
+ }
1036
+ }
1037
+ start() {
1038
+ if (this.isActive)
1039
+ return;
1040
+ this.isActive = true;
1041
+ this.attachTouchListeners();
1042
+ }
1043
+ addTarget(selector) {
1044
+ if (!this.targetSelectors.includes(selector)) {
1045
+ this.targetSelectors.push(selector);
1046
+ if (this.isActive) {
1047
+ this.attachTouchListenersForSelector(selector);
1048
+ }
1049
+ }
1050
+ }
1051
+ attachTouchListeners() {
1052
+ this.targetSelectors.forEach((selector) => {
1053
+ this.attachTouchListenersForSelector(selector);
1054
+ });
1055
+ }
1056
+ attachTouchListenersForSelector(selector) {
1057
+ const elements = document.querySelectorAll(selector);
1058
+ elements.forEach((element) => {
1059
+ if (this.touchStartListeners.has(element))
1060
+ return;
1061
+ const startListener = (e) => {
1062
+ const touchEvent = e;
1063
+ if (touchEvent.touches.length !== 1)
1064
+ return;
1065
+ const touch = touchEvent.touches[0];
1066
+ this.activeTouches.set(touch.identifier, {
1067
+ x: touch.clientX,
1068
+ y: touch.clientY,
1069
+ timestamp: Date.now(),
1070
+ element
1071
+ });
1072
+ };
1073
+ const endListener = (e) => {
1074
+ const touchEvent = e;
1075
+ if (touchEvent.changedTouches.length !== 1)
1076
+ return;
1077
+ const touch = touchEvent.changedTouches[0];
1078
+ const startData = this.activeTouches.get(touch.identifier);
1079
+ if (!startData || startData.element !== element)
1080
+ return;
1081
+ const now = Date.now();
1082
+ const duration = now - startData.timestamp;
1083
+ const dx = touch.clientX - startData.x;
1084
+ const dy = touch.clientY - startData.y;
1085
+ const movement = Math.sqrt(dx * dx + dy * dy);
1086
+ const rect = element.getBoundingClientRect();
1087
+ const inViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
1088
+ const tx = touch.clientX;
1089
+ const ty = touch.clientY;
1090
+ const overElement = tx >= rect.left && tx <= rect.right && ty >= rect.top && ty <= rect.bottom;
1091
+ let position;
1092
+ if (!overElement) {
1093
+ position = "outside";
1094
+ } else {
1095
+ const centerX = rect.left + rect.width / 2;
1096
+ const centerY = rect.top + rect.height / 2;
1097
+ const distanceFromCenter = Math.sqrt((tx - centerX) ** 2 + (ty - centerY) ** 2);
1098
+ position = distanceFromCenter < 2 ? "dead-center" : "over-element";
1099
+ }
1100
+ this.events.push({
1101
+ x: tx,
1102
+ y: ty,
1103
+ rect,
1104
+ inViewport,
1105
+ position,
1106
+ duration,
1107
+ movement,
1108
+ timestamp: now
1109
+ });
1110
+ this.notifyEvent(1);
1111
+ this.activeTouches.delete(touch.identifier);
1112
+ };
1113
+ element.addEventListener("touchstart", startListener, { passive: true });
1114
+ element.addEventListener("touchend", endListener, { passive: true });
1115
+ this.touchStartListeners.set(element, startListener);
1116
+ this.touchEndListeners.set(element, endListener);
1117
+ });
1118
+ }
1119
+ stop() {
1120
+ if (!this.isActive)
1121
+ return;
1122
+ this.isActive = false;
1123
+ this.touchStartListeners.forEach((listener, element) => {
1124
+ element.removeEventListener("touchstart", listener);
1125
+ });
1126
+ this.touchStartListeners.clear();
1127
+ this.touchEndListeners.forEach((listener, element) => {
1128
+ element.removeEventListener("touchend", listener);
1129
+ });
1130
+ this.touchEndListeners.clear();
1131
+ this.activeTouches.clear();
1132
+ }
1133
+ reset() {
1134
+ this.events = [];
1135
+ this.activeTouches.clear();
1136
+ }
1137
+ score() {
1138
+ if (this.events.length === 0)
1139
+ return void 0;
1140
+ let score = 0;
1141
+ let factors = 0;
1142
+ let positionScore = 0;
1143
+ for (const tap of this.events) {
1144
+ switch (tap.position) {
1145
+ case "outside":
1146
+ positionScore += 0;
1147
+ break;
1148
+ case "dead-center":
1149
+ positionScore += 0.5;
1150
+ break;
1151
+ case "over-element":
1152
+ positionScore += 1;
1153
+ break;
1154
+ }
1155
+ }
1156
+ score += positionScore / this.events.length;
1157
+ factors++;
1158
+ const durations = this.events.map((e) => e.duration);
1159
+ if (durations.length >= 2) {
1160
+ const durationAnalysis = analyzeIntervals(durations);
1161
+ if (durationAnalysis) {
1162
+ const { statistics, allIdentical } = durationAnalysis;
1163
+ if (allIdentical) {
1164
+ return 0.05;
1165
+ }
1166
+ score += scoreCoefficientOfVariation(statistics.cv);
1167
+ factors++;
1168
+ score += gaussian(statistics.mean, 95, 40);
1169
+ factors++;
1170
+ }
1171
+ }
1172
+ const movements = this.events.map((e) => e.movement);
1173
+ if (movements.length > 0) {
1174
+ const avgMovement = movements.reduce((a, b) => a + b, 0) / movements.length;
1175
+ score += gaussian(avgMovement, 2, 3);
1176
+ factors++;
1177
+ }
1178
+ if (this.events.length >= 3) {
1179
+ const intervals = [];
1180
+ for (let i = 1; i < this.events.length; i++) {
1181
+ intervals.push(this.events[i].timestamp - this.events[i - 1].timestamp);
1182
+ }
1183
+ const intervalAnalysis = analyzeIntervals(intervals);
1184
+ if (intervalAnalysis && !intervalAnalysis.allIdentical) {
1185
+ score += scoreCoefficientOfVariation(intervalAnalysis.statistics.cv);
1186
+ factors++;
1187
+ }
1188
+ }
1189
+ return factors > 0 ? score / factors : void 0;
1190
+ }
1191
+ getDebugInfo() {
1192
+ return {
1193
+ eventCount: this.events.length,
1194
+ positions: {
1195
+ outside: this.events.filter((e) => e.position === "outside").length,
1196
+ deadCenter: this.events.filter((e) => e.position === "dead-center").length,
1197
+ overElement: this.events.filter((e) => e.position === "over-element").length
1198
+ },
1199
+ durations: this.events.map((e) => e.duration),
1200
+ movements: this.events.map((e) => e.movement),
1201
+ inViewport: this.events.filter((e) => e.inViewport).length,
1202
+ trackedElements: this.touchStartListeners.size
1203
+ };
1204
+ }
1205
+ };
1206
+
1207
+ // dist/esm/strategies/keyboard.js
1208
+ var KeyboardStrategy = class extends BaseStrategy {
1209
+ constructor(options) {
1210
+ super();
1211
+ this.name = "keyboard";
1212
+ this.defaultWeight = 0.1;
1213
+ this.events = [];
1214
+ this.targetSelectors = ['input[type="text"]', 'input[type="email"]', "textarea"];
1215
+ this.focusedElement = null;
1216
+ this.focusedElementSelector = "";
1217
+ this.lastEventTimestamp = 0;
1218
+ this.sessionPauseThreshold = 1e3;
1219
+ this.downListener = null;
1220
+ this.upListener = null;
1221
+ this.focusListeners = /* @__PURE__ */ new Map();
1222
+ this.blurListeners = /* @__PURE__ */ new Map();
1223
+ this.isActive = false;
1224
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
1225
+ this.targetSelectors = options.targetSelectors;
1226
+ }
1227
+ }
1228
+ start() {
1229
+ if (this.isActive)
1230
+ return;
1231
+ this.isActive = true;
1232
+ this.attachFocusListeners();
1233
+ this.downListener = (e) => {
1234
+ if (!this.focusedElement)
1235
+ return;
1236
+ const now = Date.now();
1237
+ const keyEvent = e;
1238
+ if (this.lastEventTimestamp > 0 && now - this.lastEventTimestamp > this.sessionPauseThreshold) {
1239
+ this.events = [];
1240
+ }
1241
+ this.events.push({
1242
+ key: keyEvent.key,
1243
+ type: "down",
1244
+ timestamp: now,
1245
+ targetElement: this.focusedElementSelector
1246
+ });
1247
+ this.notifyEvent(0.8);
1248
+ this.lastEventTimestamp = now;
1249
+ };
1250
+ this.upListener = (e) => {
1251
+ if (!this.focusedElement)
1252
+ return;
1253
+ const now = Date.now();
1254
+ const keyEvent = e;
1255
+ if (this.lastEventTimestamp > 0 && now - this.lastEventTimestamp > this.sessionPauseThreshold) {
1256
+ this.events = [];
1257
+ }
1258
+ this.events.push({
1259
+ key: keyEvent.key,
1260
+ type: "up",
1261
+ timestamp: now,
1262
+ targetElement: this.focusedElementSelector
1263
+ });
1264
+ this.lastEventTimestamp = now;
1265
+ };
1266
+ document.addEventListener("keydown", this.downListener);
1267
+ document.addEventListener("keyup", this.upListener);
1268
+ }
1269
+ /**
1270
+ * Add a new target selector at runtime
1271
+ */
1272
+ addTarget(selector) {
1273
+ if (!this.targetSelectors.includes(selector)) {
1274
+ this.targetSelectors.push(selector);
1275
+ if (this.isActive) {
1276
+ this.attachFocusListenersForSelector(selector);
1277
+ }
1278
+ }
1279
+ }
1280
+ attachFocusListeners() {
1281
+ this.targetSelectors.forEach((selector) => {
1282
+ this.attachFocusListenersForSelector(selector);
1283
+ });
1284
+ }
1285
+ attachFocusListenersForSelector(selector) {
1286
+ const elements = document.querySelectorAll(selector);
1287
+ elements.forEach((element) => {
1288
+ if (this.focusListeners.has(element))
1289
+ return;
1290
+ const focusListener = () => {
1291
+ this.focusedElement = element;
1292
+ this.focusedElementSelector = selector;
1293
+ };
1294
+ const blurListener = () => {
1295
+ this.focusedElement = null;
1296
+ this.focusedElementSelector = "";
1297
+ };
1298
+ element.addEventListener("focus", focusListener);
1299
+ element.addEventListener("blur", blurListener);
1300
+ this.focusListeners.set(element, focusListener);
1301
+ this.blurListeners.set(element, blurListener);
1302
+ });
1303
+ }
1304
+ stop() {
1305
+ if (!this.isActive)
1306
+ return;
1307
+ this.isActive = false;
1308
+ if (this.downListener) {
1309
+ document.removeEventListener("keydown", this.downListener);
1310
+ this.downListener = null;
1311
+ }
1312
+ if (this.upListener) {
1313
+ document.removeEventListener("keyup", this.upListener);
1314
+ this.upListener = null;
1315
+ }
1316
+ this.focusListeners.forEach((listener, element) => {
1317
+ element.removeEventListener("focus", listener);
1318
+ });
1319
+ this.focusListeners.clear();
1320
+ this.blurListeners.forEach((listener, element) => {
1321
+ element.removeEventListener("blur", listener);
1322
+ });
1323
+ this.blurListeners.clear();
1324
+ this.focusedElement = null;
1325
+ }
1326
+ reset() {
1327
+ this.events = [];
1328
+ this.focusedElement = null;
1329
+ this.focusedElementSelector = "";
1330
+ this.lastEventTimestamp = 0;
1331
+ }
1332
+ score() {
1333
+ if (this.events.length < 6)
1334
+ return void 0;
1335
+ const downEvents = this.events.filter((e) => e.type === "down");
1336
+ if (downEvents.length < 3)
1337
+ return void 0;
1338
+ let score = 0;
1339
+ let factors = 0;
1340
+ const keystrokeIntervals = [];
1341
+ for (let i = 1; i < downEvents.length; i++) {
1342
+ keystrokeIntervals.push(downEvents[i].timestamp - downEvents[i - 1].timestamp);
1343
+ }
1344
+ const keystrokeAnalysis = analyzeIntervals(keystrokeIntervals);
1345
+ if (keystrokeAnalysis) {
1346
+ const { statistics, allIdentical } = keystrokeAnalysis;
1347
+ if (allIdentical) {
1348
+ return 0.1;
1349
+ }
1350
+ const keystrokeScore = scoreCoefficientOfVariation(statistics.cv);
1351
+ score += keystrokeScore;
1352
+ factors++;
1353
+ if (keystrokeScore <= 0.1)
1354
+ return keystrokeScore;
1355
+ }
1356
+ const pressDurations = [];
1357
+ for (let i = 0; i < this.events.length - 1; i++) {
1358
+ if (this.events[i].type === "down" && this.events[i + 1].type === "up" && this.events[i].key === this.events[i + 1].key) {
1359
+ pressDurations.push(this.events[i + 1].timestamp - this.events[i].timestamp);
1360
+ }
1361
+ }
1362
+ const pressDurationAnalysis = analyzeIntervals(pressDurations);
1363
+ if (pressDurationAnalysis) {
1364
+ const { statistics, allIdentical } = pressDurationAnalysis;
1365
+ if (allIdentical) {
1366
+ return 0.1;
1367
+ }
1368
+ if (statistics.mean < 5) {
1369
+ score += 0.1;
1370
+ factors++;
1371
+ } else {
1372
+ const pressDurationScore = scoreCoefficientOfVariation(statistics.cv);
1373
+ score += pressDurationScore;
1374
+ factors++;
1375
+ if (pressDurationScore <= 0.1)
1376
+ return pressDurationScore;
1377
+ }
1378
+ }
1379
+ const backspaceCount = this.events.filter((e) => e.key === "Backspace").length;
1380
+ if (backspaceCount > 0) {
1381
+ const backspaceRatio = backspaceCount / downEvents.length;
1382
+ if (backspaceRatio > 0.05 && backspaceRatio < 0.3) {
1383
+ score += 1;
1384
+ factors++;
1385
+ } else if (backspaceRatio > 0) {
1386
+ score += 0.8;
1387
+ factors++;
1388
+ }
1389
+ }
1390
+ return factors > 0 ? score / factors : void 0;
1391
+ }
1392
+ getDebugInfo() {
1393
+ const pressDurations = [];
1394
+ for (let i = 0; i < this.events.length - 1; i++) {
1395
+ if (this.events[i].type === "down" && this.events[i + 1].type === "up" && this.events[i].key === this.events[i + 1].key) {
1396
+ pressDurations.push(this.events[i + 1].timestamp - this.events[i].timestamp);
1397
+ }
1398
+ }
1399
+ return {
1400
+ eventCount: this.events.length,
1401
+ downEvents: this.events.filter((e) => e.type === "down").length,
1402
+ upEvents: this.events.filter((e) => e.type === "up").length,
1403
+ backspaceCount: this.events.filter((e) => e.key === "Backspace").length,
1404
+ pressDurations,
1405
+ // For graphing
1406
+ focusedElement: this.focusedElementSelector,
1407
+ trackedElements: this.focusListeners.size
1408
+ };
1409
+ }
1410
+ };
1411
+
1412
+ // dist/esm/strategies/environment.js
1413
+ var AUTOMATION_GLOBALS = [
1414
+ "__nightmare",
1415
+ "_phantom",
1416
+ "callPhantom",
1417
+ "__selenium_evaluate",
1418
+ "__webdriver_evaluate",
1419
+ "__driver_evaluate",
1420
+ "__webdriver_script_function",
1421
+ "__lastWatirAlert",
1422
+ "__lastWatirConfirm",
1423
+ "__lastWatirPrompt",
1424
+ "_Selenium_IDE_Recorder",
1425
+ "domAutomation",
1426
+ "domAutomationController",
1427
+ "webdriver",
1428
+ "_webdriver_script_fn",
1429
+ "__webdriver_script_func",
1430
+ "__fxdriver_evaluate",
1431
+ "__fxdriver_unwrapped",
1432
+ "__selenium_unwrapped"
1433
+ ];
1434
+ var EnvironmentStrategy = class extends BaseStrategy {
1435
+ constructor() {
1436
+ super(...arguments);
1437
+ this.name = "environment";
1438
+ this.defaultWeight = 0.08;
1439
+ this.data = null;
1440
+ }
1441
+ start() {
1442
+ this.captureEnvironment();
1443
+ }
1444
+ stop() {
1445
+ }
1446
+ reset() {
1447
+ this.data = null;
1448
+ }
1449
+ onTick(_timestamp) {
1450
+ this.captureEnvironment();
1451
+ }
1452
+ score() {
1453
+ if (!this.data)
1454
+ return void 0;
1455
+ const env = this.data;
1456
+ const auto = env.automation;
1457
+ if (auto.isWebdriver) {
1458
+ return 0.05;
1459
+ }
1460
+ if (auto.hasAutomationGlobals) {
1461
+ return 0.1;
1462
+ }
1463
+ if (auto.hasCDPInjection) {
1464
+ return 0.1;
1465
+ }
1466
+ const headlessIndicatorCount = [
1467
+ auto.hasHeadlessUA,
1468
+ auto.hasChromelessRuntime,
1469
+ auto.hasSoftwareRenderer
1470
+ ].filter(Boolean).length;
1471
+ if (headlessIndicatorCount >= 2) {
1472
+ return 0.15;
1473
+ }
1474
+ if (auto.hasHeadlessUA) {
1475
+ return 0.2;
1476
+ }
1477
+ let score = 0;
1478
+ let factors = 0;
1479
+ score += env.suspiciousDimensions ? 0.1 : 1;
1480
+ score += env.suspiciousRatio ? 0.2 : 1;
1481
+ score += env.featureInconsistency ? 0.3 : 1;
1482
+ factors += 3;
1483
+ const featureCount = [
1484
+ env.hasWebGL,
1485
+ env.hasLocalStorage,
1486
+ env.hasSessionStorage,
1487
+ env.hasIndexedDB
1488
+ ].filter(Boolean).length;
1489
+ score += featureCount / 4;
1490
+ factors++;
1491
+ if (!env.isMobile) {
1492
+ score += inverseSigmoid(env.plugins, -2, -0.5);
1493
+ score += env.plugins > 0 ? 1 : 0.1;
1494
+ factors += 2;
1495
+ if (auto.hasNoPlugins && env.vendor.includes("Google")) {
1496
+ score += 0.3;
1497
+ factors++;
1498
+ }
1499
+ }
1500
+ if (auto.hasChromelessRuntime) {
1501
+ score += 0.3;
1502
+ factors++;
1503
+ }
1504
+ if (auto.hasSoftwareRenderer) {
1505
+ score += 0.4;
1506
+ factors++;
1507
+ }
1508
+ score += gaussian(env.devicePixelRatio, 2, 1.5);
1509
+ score += env.colorDepth === 24 || env.colorDepth === 32 ? 1 : 0.4;
1510
+ factors += 2;
1511
+ return factors > 0 ? score / factors : void 0;
1512
+ }
1513
+ isMobileDevice() {
1514
+ const hasTouchScreen = navigator.maxTouchPoints > 0 || "ontouchstart" in window;
1515
+ const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1516
+ const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
1517
+ return hasTouchScreen && smallScreen || mobileUA;
1518
+ }
1519
+ /**
1520
+ * Detect automation indicators
1521
+ */
1522
+ detectAutomation() {
1523
+ const isMobile = this.isMobileDevice();
1524
+ const isWebdriver = navigator.webdriver === true;
1525
+ const ua = navigator.userAgent;
1526
+ const hasHeadlessUA = /HeadlessChrome|Headless/i.test(ua);
1527
+ const win = window;
1528
+ const hasChromelessRuntime = !!(win.chrome && typeof win.chrome === "object" && !win.chrome.runtime);
1529
+ const detectedGlobals = [];
1530
+ for (const global of AUTOMATION_GLOBALS) {
1531
+ if (global in win) {
1532
+ detectedGlobals.push(global);
1533
+ }
1534
+ }
1535
+ const hasAutomationGlobals = detectedGlobals.length > 0;
1536
+ const detectedCDPKeys = [];
1537
+ try {
1538
+ for (const key of Object.keys(win)) {
1539
+ if (/^cdc_|^__cdc_/.test(key)) {
1540
+ detectedCDPKeys.push(key);
1541
+ }
1542
+ }
1543
+ } catch (_a) {
1544
+ }
1545
+ const hasCDPInjection = detectedCDPKeys.length > 0;
1546
+ const hasNoPlugins = !isMobile && navigator.plugins.length === 0;
1547
+ let hasSoftwareRenderer = false;
1548
+ try {
1549
+ const canvas = document.createElement("canvas");
1550
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1551
+ if (gl) {
1552
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
1553
+ if (debugInfo) {
1554
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
1555
+ hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
1556
+ this._webglRenderer = renderer;
1557
+ }
1558
+ }
1559
+ } catch (_b) {
1560
+ }
1561
+ return {
1562
+ isWebdriver,
1563
+ hasHeadlessUA,
1564
+ hasChromelessRuntime,
1565
+ hasSoftwareRenderer,
1566
+ hasNoPlugins,
1567
+ hasAutomationGlobals,
1568
+ detectedGlobals,
1569
+ hasCDPInjection,
1570
+ detectedCDPKeys
1571
+ };
1572
+ }
1573
+ captureEnvironment() {
1574
+ try {
1575
+ let hasWebGL = false;
1576
+ let webglRenderer;
1577
+ try {
1578
+ const canvas = document.createElement("canvas");
1579
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
1580
+ hasWebGL = !!gl;
1581
+ if (gl) {
1582
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
1583
+ if (debugInfo) {
1584
+ webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
1585
+ }
1586
+ }
1587
+ } catch (e) {
1588
+ hasWebGL = false;
1589
+ }
1590
+ const hasWebRTC = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
1591
+ const isMobile = this.isMobileDevice();
1592
+ const windowWidth = window.innerWidth;
1593
+ const windowHeight = window.innerHeight;
1594
+ const screenWidth = window.screen.width;
1595
+ const screenHeight = window.screen.height;
1596
+ const suspiciousDimensions = windowWidth === 800 && windowHeight === 600 || windowWidth === 1024 && windowHeight === 768 || windowWidth === 1280 && windowHeight === 720 || screenWidth === 800 && screenHeight === 600;
1597
+ const windowScreenRatio = windowWidth * windowHeight / (screenWidth * screenHeight);
1598
+ const suspiciousRatio = windowScreenRatio === 1 || windowScreenRatio < 0.1 || windowScreenRatio > 1;
1599
+ const hasStorage = typeof localStorage !== "undefined" && typeof sessionStorage !== "undefined";
1600
+ const featureInconsistency = navigator.plugins.length === 0 && navigator.mimeTypes.length === 0 || !hasWebGL || !hasStorage;
1601
+ const automation = this.detectAutomation();
1602
+ this.data = {
1603
+ screenWidth,
1604
+ screenHeight,
1605
+ windowWidth,
1606
+ windowHeight,
1607
+ devicePixelRatio: window.devicePixelRatio,
1608
+ colorDepth: window.screen.colorDepth,
1609
+ userAgent: navigator.userAgent,
1610
+ platform: navigator.platform,
1611
+ language: navigator.language,
1612
+ languages: navigator.languages ? Array.from(navigator.languages) : [navigator.language],
1613
+ hardwareConcurrency: navigator.hardwareConcurrency,
1614
+ maxTouchPoints: navigator.maxTouchPoints || 0,
1615
+ vendor: navigator.vendor,
1616
+ hasWebGL,
1617
+ hasWebRTC,
1618
+ hasLocalStorage: typeof localStorage !== "undefined",
1619
+ hasSessionStorage: typeof sessionStorage !== "undefined",
1620
+ hasIndexedDB: "indexedDB" in window,
1621
+ plugins: navigator.plugins.length,
1622
+ mimeTypes: navigator.mimeTypes.length,
1623
+ suspiciousRatio,
1624
+ suspiciousDimensions,
1625
+ featureInconsistency,
1626
+ isMobile,
1627
+ timestamp: Date.now(),
1628
+ automation,
1629
+ webglRenderer
1630
+ };
1631
+ } catch (_a) {
1632
+ }
1633
+ }
1634
+ getDebugInfo() {
1635
+ return this.data;
1636
+ }
1637
+ /**
1638
+ * Check if current device is mobile
1639
+ * Returns true if mobile, false otherwise
1640
+ * Returns null if environment hasn't been captured yet
1641
+ */
1642
+ isMobile() {
1643
+ var _a, _b;
1644
+ return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.isMobile) !== null && _b !== void 0 ? _b : null;
1645
+ }
1646
+ /**
1647
+ * Get automation detection results
1648
+ * Returns null if environment hasn't been captured yet
1649
+ */
1650
+ getAutomationIndicators() {
1651
+ var _a, _b;
1652
+ return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.automation) !== null && _b !== void 0 ? _b : null;
1653
+ }
1654
+ /**
1655
+ * Quick check if any strong automation indicator is present
1656
+ * Returns true if likely a bot, false if likely human, null if not checked yet
1657
+ */
1658
+ isLikelyBot() {
1659
+ if (!this.data)
1660
+ return null;
1661
+ const auto = this.data.automation;
1662
+ return auto.isWebdriver || auto.hasAutomationGlobals || auto.hasCDPInjection || auto.hasHeadlessUA;
1663
+ }
1664
+ };
1665
+
1666
+ // dist/esm/strategies/resize.js
1667
+ var ResizeStrategy = class extends BaseStrategy {
1668
+ constructor() {
1669
+ super(...arguments);
1670
+ this.name = "resize";
1671
+ this.defaultWeight = 0.02;
1672
+ this.events = [];
1673
+ this.listener = null;
1674
+ this.isActive = false;
1675
+ this.lastMousePosition = null;
1676
+ this.mouseListener = null;
1677
+ }
1678
+ start() {
1679
+ if (this.isActive)
1680
+ return;
1681
+ this.isActive = true;
1682
+ this.mouseListener = (e) => {
1683
+ const mouseEvent = e;
1684
+ this.lastMousePosition = {
1685
+ x: mouseEvent.clientX,
1686
+ y: mouseEvent.clientY
1687
+ };
1688
+ };
1689
+ document.addEventListener("mousemove", this.mouseListener, { passive: true });
1690
+ this.listener = () => {
1691
+ var _a, _b;
1692
+ const mouseX = (_a = this.lastMousePosition) === null || _a === void 0 ? void 0 : _a.x;
1693
+ const mouseY = (_b = this.lastMousePosition) === null || _b === void 0 ? void 0 : _b.y;
1694
+ let mouseNearEdge = false;
1695
+ if (mouseX !== void 0 && mouseY !== void 0) {
1696
+ const edgeThreshold = 50;
1697
+ mouseNearEdge = mouseX < edgeThreshold || mouseX > window.innerWidth - edgeThreshold || mouseY < edgeThreshold || mouseY > window.innerHeight - edgeThreshold;
1698
+ }
1699
+ this.events.push({
1700
+ width: window.innerWidth,
1701
+ height: window.innerHeight,
1702
+ mouseX,
1703
+ mouseY,
1704
+ mouseNearEdge,
1705
+ timestamp: Date.now()
1706
+ });
1707
+ };
1708
+ window.addEventListener("resize", this.listener);
1709
+ }
1710
+ stop() {
1711
+ if (!this.isActive)
1712
+ return;
1713
+ this.isActive = false;
1714
+ if (this.listener) {
1715
+ window.removeEventListener("resize", this.listener);
1716
+ this.listener = null;
1717
+ }
1718
+ if (this.mouseListener) {
1719
+ document.removeEventListener("mousemove", this.mouseListener);
1720
+ this.mouseListener = null;
1721
+ }
1722
+ }
1723
+ reset() {
1724
+ this.events = [];
1725
+ }
1726
+ score() {
1727
+ if (this.events.length === 0)
1728
+ return void 0;
1729
+ let score = 0;
1730
+ let factors = 0;
1731
+ score += inverseSigmoid(this.events.length, 5, 0.5);
1732
+ factors++;
1733
+ const withMouse = this.events.filter((e) => e.mouseX !== void 0);
1734
+ if (withMouse.length > 0) {
1735
+ const nearEdge = withMouse.filter((e) => e.mouseNearEdge).length;
1736
+ score += sigmoid(nearEdge / withMouse.length, 0.5, 8);
1737
+ factors++;
1738
+ }
1739
+ return factors > 0 ? score / factors : void 0;
1740
+ }
1741
+ getDebugInfo() {
1742
+ return {
1743
+ eventCount: this.events.length,
1744
+ withMouseData: this.events.filter((e) => e.mouseX !== void 0).length
1745
+ };
1746
+ }
1747
+ };
1748
+
1749
+ // dist/esm/strategies/timing.js
1750
+ var TimingStrategy = class extends BaseStrategy {
1751
+ constructor() {
1752
+ super(...arguments);
1753
+ this.name = "timing";
1754
+ this.defaultWeight = 0.15;
1755
+ this.actions = [];
1756
+ this.mouseStillness = {
1757
+ position: null,
1758
+ lastMoveTime: 0,
1759
+ microMovementCount: 0
1760
+ };
1761
+ this.microMovements = [];
1762
+ this.isActive = false;
1763
+ this.mouseListener = null;
1764
+ this.clickListener = null;
1765
+ this.keydownListener = null;
1766
+ this.scrollListener = null;
1767
+ this.STILLNESS_WINDOW = 500;
1768
+ this.MICRO_MOVEMENT_THRESHOLD = 1;
1769
+ this.MAX_MICRO_MOVEMENT = 5;
1770
+ this.MACHINE_PRECISION_THRESHOLD = 5;
1771
+ }
1772
+ start() {
1773
+ if (this.isActive)
1774
+ return;
1775
+ this.isActive = true;
1776
+ this.mouseListener = (e) => {
1777
+ const mouseEvent = e;
1778
+ const now = Date.now();
1779
+ const currentPos = { x: mouseEvent.clientX, y: mouseEvent.clientY };
1780
+ if (this.mouseStillness.position) {
1781
+ const dx = currentPos.x - this.mouseStillness.position.x;
1782
+ const dy = currentPos.y - this.mouseStillness.position.y;
1783
+ const distance = Math.sqrt(dx * dx + dy * dy);
1784
+ if (distance >= this.MICRO_MOVEMENT_THRESHOLD && distance <= this.MAX_MICRO_MOVEMENT) {
1785
+ this.microMovements.push({ timestamp: now, distance });
1786
+ }
1787
+ if (distance >= this.MICRO_MOVEMENT_THRESHOLD) {
1788
+ this.mouseStillness.lastMoveTime = now;
1789
+ }
1790
+ }
1791
+ this.mouseStillness.position = currentPos;
1792
+ const cutoff = now - this.STILLNESS_WINDOW;
1793
+ this.microMovements = this.microMovements.filter((m) => m.timestamp >= cutoff);
1794
+ };
1795
+ this.clickListener = (e) => {
1796
+ this.recordAction("click", e);
1797
+ };
1798
+ this.keydownListener = () => {
1799
+ this.recordAction("keydown");
1800
+ };
1801
+ this.scrollListener = () => {
1802
+ this.recordAction("scroll");
1803
+ };
1804
+ document.addEventListener("mousemove", this.mouseListener, { passive: true });
1805
+ document.addEventListener("click", this.clickListener, { passive: true });
1806
+ document.addEventListener("keydown", this.keydownListener, { passive: true });
1807
+ document.addEventListener("scroll", this.scrollListener, { passive: true });
1808
+ }
1809
+ recordAction(type, _event) {
1810
+ const now = Date.now();
1811
+ const timeSinceMouseMove = this.mouseStillness.lastMoveTime > 0 ? now - this.mouseStillness.lastMoveTime : -1;
1812
+ this.mouseStillness.microMovementCount = this.microMovements.length;
1813
+ this.actions.push({
1814
+ type,
1815
+ timestamp: now,
1816
+ timeSinceMouseMove,
1817
+ wasHidden: document.hidden
1818
+ });
1819
+ this.notifyEvent(0.7);
1820
+ if (this.actions.length > 100) {
1821
+ this.actions.shift();
1822
+ }
1823
+ }
1824
+ stop() {
1825
+ if (!this.isActive)
1826
+ return;
1827
+ this.isActive = false;
1828
+ if (this.mouseListener) {
1829
+ document.removeEventListener("mousemove", this.mouseListener);
1830
+ this.mouseListener = null;
1831
+ }
1832
+ if (this.clickListener) {
1833
+ document.removeEventListener("click", this.clickListener);
1834
+ this.clickListener = null;
1835
+ }
1836
+ if (this.keydownListener) {
1837
+ document.removeEventListener("keydown", this.keydownListener);
1838
+ this.keydownListener = null;
1839
+ }
1840
+ if (this.scrollListener) {
1841
+ document.removeEventListener("scroll", this.scrollListener);
1842
+ this.scrollListener = null;
1843
+ }
1844
+ }
1845
+ reset() {
1846
+ this.actions = [];
1847
+ this.microMovements = [];
1848
+ this.mouseStillness = {
1849
+ position: null,
1850
+ lastMoveTime: 0,
1851
+ microMovementCount: 0
1852
+ };
1853
+ }
1854
+ score() {
1855
+ const actionCount = this.actions.length;
1856
+ if (actionCount < 3)
1857
+ return void 0;
1858
+ let score = 0;
1859
+ let factors = 0;
1860
+ const clicks = [];
1861
+ const clicksWithMouse = [];
1862
+ let hiddenCount = 0;
1863
+ for (const action of this.actions) {
1864
+ if (action.type === "click") {
1865
+ clicks.push(action);
1866
+ if (action.timeSinceMouseMove >= 0) {
1867
+ clicksWithMouse.push(action);
1868
+ }
1869
+ }
1870
+ if (action.wasHidden)
1871
+ hiddenCount++;
1872
+ }
1873
+ if (clicks.length >= 2) {
1874
+ const stillnessScore = this.scorePreActionStillness(clicks);
1875
+ if (stillnessScore !== void 0) {
1876
+ score += stillnessScore;
1877
+ factors++;
1878
+ }
1879
+ }
1880
+ if (clicksWithMouse.length >= 3) {
1881
+ const mouseToClickScore = this.scoreMouseToClickDelayFromCache(clicksWithMouse);
1882
+ if (mouseToClickScore !== void 0) {
1883
+ score += mouseToClickScore;
1884
+ factors++;
1885
+ }
1886
+ }
1887
+ if (actionCount >= 5) {
1888
+ const intervalScore = this.scoreActionIntervals();
1889
+ if (intervalScore !== void 0) {
1890
+ score += intervalScore;
1891
+ factors++;
1892
+ }
1893
+ }
1894
+ const hiddenScore = this.scoreHiddenActionsFromCount(hiddenCount, actionCount);
1895
+ score += hiddenScore;
1896
+ factors++;
1897
+ return factors > 0 ? score / factors : void 0;
1898
+ }
1899
+ /**
1900
+ * Score based on pre-action stillness
1901
+ * Humans have micro-movements before clicking; bots are perfectly still
1902
+ */
1903
+ scorePreActionStillness(clicks) {
1904
+ const clicksWithMouseData = clicks.filter((c) => c.timeSinceMouseMove >= 0);
1905
+ if (clicksWithMouseData.length < 2)
1906
+ return void 0;
1907
+ const perfectlyStillClicks = clicksWithMouseData.filter((c) => {
1908
+ return c.timeSinceMouseMove >= 100 && c.timeSinceMouseMove <= 500 && this.mouseStillness.microMovementCount === 0;
1909
+ }).length;
1910
+ const stillRatio = perfectlyStillClicks / clicksWithMouseData.length;
1911
+ if (stillRatio >= 0.9)
1912
+ return 0.1;
1913
+ if (stillRatio >= 0.7)
1914
+ return 0.3;
1915
+ if (stillRatio >= 0.5)
1916
+ return 0.5;
1917
+ if (stillRatio >= 0.3)
1918
+ return 0.7;
1919
+ return 1;
1920
+ }
1921
+ /**
1922
+ * Score mouse-to-click delay patterns (using pre-filtered cache)
1923
+ */
1924
+ scoreMouseToClickDelayFromCache(clicksWithMouse) {
1925
+ const delays = clicksWithMouse.map((c) => c.timeSinceMouseMove);
1926
+ const stats = calculateStatistics(delays);
1927
+ if (stats.mean < 10)
1928
+ return 0.1;
1929
+ if (stats.cv < 0.2 && clicksWithMouse.length >= 5)
1930
+ return 0.2;
1931
+ if (stats.mean >= 50 && stats.mean <= 300 && stats.cv >= 0.3)
1932
+ return 1;
1933
+ if (stats.cv < 0.4)
1934
+ return 0.6;
1935
+ return 0.8;
1936
+ }
1937
+ /**
1938
+ * Score action intervals for machine-like precision
1939
+ * Detects exact intervals: 100ms, 500ms, 1000ms
1940
+ */
1941
+ scoreActionIntervals() {
1942
+ if (this.actions.length < 5)
1943
+ return void 0;
1944
+ const intervals = [];
1945
+ for (let i = 1; i < this.actions.length; i++) {
1946
+ intervals.push(this.actions[i].timestamp - this.actions[i - 1].timestamp);
1947
+ }
1948
+ const machineIntervals = [100, 200, 250, 500, 1e3];
1949
+ let preciseCount = 0;
1950
+ for (const interval of intervals) {
1951
+ for (const machineInterval of machineIntervals) {
1952
+ const remainder = interval % machineInterval;
1953
+ if (remainder < this.MACHINE_PRECISION_THRESHOLD || machineInterval - remainder < this.MACHINE_PRECISION_THRESHOLD) {
1954
+ preciseCount++;
1955
+ break;
1956
+ }
1957
+ }
1958
+ }
1959
+ const preciseRatio = preciseCount / intervals.length;
1960
+ if (preciseRatio >= 0.8)
1961
+ return 0.1;
1962
+ if (preciseRatio >= 0.6)
1963
+ return 0.3;
1964
+ if (preciseRatio >= 0.4)
1965
+ return 0.5;
1966
+ const stats = calculateStatistics(intervals);
1967
+ if (stats.cv < 0.15)
1968
+ return 0.2;
1969
+ return 1;
1970
+ }
1971
+ /**
1972
+ * Score based on actions while document was hidden (using pre-computed count)
1973
+ */
1974
+ scoreHiddenActionsFromCount(hiddenCount, totalCount) {
1975
+ if (hiddenCount === 0)
1976
+ return 1;
1977
+ const hiddenRatio = hiddenCount / totalCount;
1978
+ if (hiddenRatio > 0.5)
1979
+ return 0.1;
1980
+ if (hiddenRatio > 0.2)
1981
+ return 0.3;
1982
+ if (hiddenRatio > 0)
1983
+ return 0.5;
1984
+ return 1;
1985
+ }
1986
+ getDebugInfo() {
1987
+ const clicks = this.actions.filter((a) => a.type === "click");
1988
+ const intervals = [];
1989
+ for (let i = 1; i < this.actions.length; i++) {
1990
+ intervals.push(this.actions[i].timestamp - this.actions[i - 1].timestamp);
1991
+ }
1992
+ return {
1993
+ actionCount: this.actions.length,
1994
+ clickCount: clicks.length,
1995
+ keydownCount: this.actions.filter((a) => a.type === "keydown").length,
1996
+ scrollCount: this.actions.filter((a) => a.type === "scroll").length,
1997
+ hiddenActionCount: this.actions.filter((a) => a.wasHidden).length,
1998
+ microMovementCount: this.microMovements.length,
1999
+ intervals: intervals.slice(-20),
2000
+ // Last 20 intervals
2001
+ lastStillnessDuration: this.mouseStillness.lastMoveTime > 0 ? Date.now() - this.mouseStillness.lastMoveTime : null
2002
+ };
2003
+ }
2004
+ };
2005
+
2006
+ // dist/esm/strategies/visibility.js
2007
+ var VisibilityStrategy = class extends BaseStrategy {
2008
+ constructor() {
2009
+ super(...arguments);
2010
+ this.name = "visibility";
2011
+ this.defaultWeight = 0.1;
2012
+ this.events = [];
2013
+ this.focusTypingPairs = [];
2014
+ this.actionsWhileHidden = 0;
2015
+ this.lastVisibilityChange = null;
2016
+ this.resumeDelays = [];
2017
+ this.isActive = false;
2018
+ this.visibilityListener = null;
2019
+ this.focusListener = null;
2020
+ this.blurListener = null;
2021
+ this.clickListener = null;
2022
+ this.keydownListener = null;
2023
+ this.inputFocusListener = null;
2024
+ this.lastFocusedInput = null;
2025
+ this.hasTypedInFocusedInput = false;
2026
+ this.lastActionTime = 0;
2027
+ this.preHideActionTime = 0;
2028
+ }
2029
+ start() {
2030
+ if (this.isActive)
2031
+ return;
2032
+ this.isActive = true;
2033
+ this.visibilityListener = () => {
2034
+ const now = Date.now();
2035
+ const wasHidden = document.hidden;
2036
+ if (this.lastVisibilityChange && !wasHidden) {
2037
+ if (this.lastActionTime > 0 && this.preHideActionTime > 0) {
2038
+ const timeSinceLastAction = now - this.lastActionTime;
2039
+ if (timeSinceLastAction < 50) {
2040
+ this.actionsWhileHidden++;
2041
+ }
2042
+ }
2043
+ this.lastVisibilityChange = { hidden: false, timestamp: now };
2044
+ } else if (wasHidden) {
2045
+ this.preHideActionTime = this.lastActionTime;
2046
+ this.lastVisibilityChange = { hidden: true, timestamp: now };
2047
+ }
2048
+ this.events.push({
2049
+ type: "visibility_change",
2050
+ timestamp: now,
2051
+ wasHidden
2052
+ });
2053
+ };
2054
+ this.focusListener = () => {
2055
+ this.events.push({
2056
+ type: "focus_change",
2057
+ timestamp: Date.now(),
2058
+ wasHidden: document.hidden,
2059
+ wasFocused: true
2060
+ });
2061
+ };
2062
+ this.blurListener = () => {
2063
+ this.events.push({
2064
+ type: "focus_change",
2065
+ timestamp: Date.now(),
2066
+ wasHidden: document.hidden,
2067
+ wasFocused: false
2068
+ });
2069
+ };
2070
+ this.clickListener = (e) => {
2071
+ this.recordAction("click", e);
2072
+ };
2073
+ this.keydownListener = (e) => {
2074
+ this.recordAction("keydown", e);
2075
+ if (this.lastFocusedInput && !this.hasTypedInFocusedInput) {
2076
+ const now = Date.now();
2077
+ const delay = now - this.lastFocusedInput.timestamp;
2078
+ this.focusTypingPairs.push({
2079
+ focusTime: this.lastFocusedInput.timestamp,
2080
+ firstKeypressTime: now,
2081
+ delay,
2082
+ element: this.lastFocusedInput.element
2083
+ });
2084
+ this.hasTypedInFocusedInput = true;
2085
+ }
2086
+ };
2087
+ this.inputFocusListener = (e) => {
2088
+ const target = e.target;
2089
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA")) {
2090
+ this.lastFocusedInput = {
2091
+ element: target.tagName.toLowerCase(),
2092
+ timestamp: Date.now()
2093
+ };
2094
+ this.hasTypedInFocusedInput = false;
2095
+ }
2096
+ };
2097
+ document.addEventListener("visibilitychange", this.visibilityListener);
2098
+ window.addEventListener("focus", this.focusListener);
2099
+ window.addEventListener("blur", this.blurListener);
2100
+ document.addEventListener("click", this.clickListener, { passive: true });
2101
+ document.addEventListener("keydown", this.keydownListener, { passive: true });
2102
+ document.addEventListener("focusin", this.inputFocusListener, { passive: true });
2103
+ }
2104
+ recordAction(_type, _event) {
2105
+ const now = Date.now();
2106
+ this.lastActionTime = now;
2107
+ if (document.hidden) {
2108
+ this.actionsWhileHidden++;
2109
+ this.events.push({
2110
+ type: "action_while_hidden",
2111
+ timestamp: now,
2112
+ wasHidden: true
2113
+ });
2114
+ this.notifyEvent(1);
2115
+ }
2116
+ if (this.lastVisibilityChange && !this.lastVisibilityChange.hidden) {
2117
+ const resumeDelay = now - this.lastVisibilityChange.timestamp;
2118
+ if (resumeDelay > 0 && resumeDelay < 1e4 && this.resumeDelays.length < 20) {
2119
+ const lastRecordedDelay = this.resumeDelays[this.resumeDelays.length - 1];
2120
+ if (lastRecordedDelay === void 0 || Math.abs(resumeDelay - lastRecordedDelay) > 100) {
2121
+ this.resumeDelays.push(resumeDelay);
2122
+ }
2123
+ }
2124
+ }
2125
+ }
2126
+ stop() {
2127
+ if (!this.isActive)
2128
+ return;
2129
+ this.isActive = false;
2130
+ if (this.visibilityListener) {
2131
+ document.removeEventListener("visibilitychange", this.visibilityListener);
2132
+ this.visibilityListener = null;
2133
+ }
2134
+ if (this.focusListener) {
2135
+ window.removeEventListener("focus", this.focusListener);
2136
+ this.focusListener = null;
2137
+ }
2138
+ if (this.blurListener) {
2139
+ window.removeEventListener("blur", this.blurListener);
2140
+ this.blurListener = null;
2141
+ }
2142
+ if (this.clickListener) {
2143
+ document.removeEventListener("click", this.clickListener);
2144
+ this.clickListener = null;
2145
+ }
2146
+ if (this.keydownListener) {
2147
+ document.removeEventListener("keydown", this.keydownListener);
2148
+ this.keydownListener = null;
2149
+ }
2150
+ if (this.inputFocusListener) {
2151
+ document.removeEventListener("focusin", this.inputFocusListener);
2152
+ this.inputFocusListener = null;
2153
+ }
2154
+ }
2155
+ reset() {
2156
+ this.events = [];
2157
+ this.focusTypingPairs = [];
2158
+ this.actionsWhileHidden = 0;
2159
+ this.lastVisibilityChange = null;
2160
+ this.resumeDelays = [];
2161
+ this.lastFocusedInput = null;
2162
+ this.hasTypedInFocusedInput = false;
2163
+ this.lastActionTime = 0;
2164
+ this.preHideActionTime = 0;
2165
+ }
2166
+ score() {
2167
+ if (this.events.length < 2)
2168
+ return void 0;
2169
+ let score = 0;
2170
+ let factors = 0;
2171
+ const hiddenActionScore = this.scoreHiddenActions();
2172
+ if (hiddenActionScore !== void 0) {
2173
+ score += hiddenActionScore;
2174
+ factors++;
2175
+ }
2176
+ const resumeScore = this.scoreResumeDelays();
2177
+ if (resumeScore !== void 0) {
2178
+ score += resumeScore;
2179
+ factors++;
2180
+ }
2181
+ const focusTypingScore = this.scoreFocusTyping();
2182
+ if (focusTypingScore !== void 0) {
2183
+ score += focusTypingScore;
2184
+ factors++;
2185
+ }
2186
+ return factors > 0 ? score / factors : void 0;
2187
+ }
2188
+ /**
2189
+ * Score based on actions while document was hidden
2190
+ * Humans can't interact with hidden tabs; bots can
2191
+ */
2192
+ scoreHiddenActions() {
2193
+ if (this.actionsWhileHidden === 0)
2194
+ return 1;
2195
+ if (this.actionsWhileHidden >= 5)
2196
+ return 0.1;
2197
+ if (this.actionsWhileHidden >= 3)
2198
+ return 0.2;
2199
+ if (this.actionsWhileHidden >= 1)
2200
+ return 0.3;
2201
+ return 1;
2202
+ }
2203
+ /**
2204
+ * Score based on how quickly activity resumes after tab becomes visible
2205
+ * Humans need time to refocus (100-500ms minimum)
2206
+ * Bots often resume instantly (< 50ms)
2207
+ */
2208
+ scoreResumeDelays() {
2209
+ if (this.resumeDelays.length < 2)
2210
+ return void 0;
2211
+ const fastResumeCount = this.resumeDelays.filter((d) => d < 50).length;
2212
+ const fastResumeRatio = fastResumeCount / this.resumeDelays.length;
2213
+ if (fastResumeRatio >= 0.8)
2214
+ return 0.1;
2215
+ if (fastResumeRatio >= 0.5)
2216
+ return 0.3;
2217
+ if (fastResumeRatio >= 0.3)
2218
+ return 0.5;
2219
+ if (fastResumeRatio > 0)
2220
+ return 0.7;
2221
+ return 1;
2222
+ }
2223
+ /**
2224
+ * Score based on focus-to-keypress timing
2225
+ * Humans focus inputs before typing (natural delay 100-500ms)
2226
+ * Bots often type without focusing or with instant delay
2227
+ */
2228
+ scoreFocusTyping() {
2229
+ if (this.focusTypingPairs.length < 2)
2230
+ return void 0;
2231
+ let suspiciousCount = 0;
2232
+ for (const pair of this.focusTypingPairs) {
2233
+ if (pair.delay < 20) {
2234
+ suspiciousCount++;
2235
+ }
2236
+ }
2237
+ const suspiciousRatio = suspiciousCount / this.focusTypingPairs.length;
2238
+ if (suspiciousRatio >= 0.8)
2239
+ return 0.2;
2240
+ if (suspiciousRatio >= 0.5)
2241
+ return 0.4;
2242
+ if (suspiciousRatio >= 0.3)
2243
+ return 0.6;
2244
+ if (suspiciousRatio > 0)
2245
+ return 0.8;
2246
+ return 1;
2247
+ }
2248
+ getDebugInfo() {
2249
+ return {
2250
+ eventCount: this.events.length,
2251
+ actionsWhileHidden: this.actionsWhileHidden,
2252
+ visibilityChanges: this.events.filter((e) => e.type === "visibility_change").length,
2253
+ focusChanges: this.events.filter((e) => e.type === "focus_change").length,
2254
+ resumeDelays: this.resumeDelays,
2255
+ focusTypingPairs: this.focusTypingPairs.map((p) => ({
2256
+ delay: p.delay,
2257
+ element: p.element
2258
+ }))
2259
+ };
2260
+ }
2261
+ };
2262
+
2263
+ // dist/esm/types.js
2264
+ var DEFAULT_SETTINGS = {
2265
+ sampleRates: {
2266
+ mouseMove: 0.1,
2267
+ // Track 10% of mouse moves
2268
+ scroll: 1,
2269
+ // Track ALL scrolls - critical for detection
2270
+ keypress: 1
2271
+ // Track all keypresses
2272
+ },
2273
+ rollingWindows: {
2274
+ mouseMove: 3e4,
2275
+ scroll: 3e4
2276
+ },
2277
+ weights: {
2278
+ mouseMovement: 0.3,
2279
+ // Increased - continuous behavioral signal
2280
+ clickAccuracy: 0.3,
2281
+ // Increased - critical behavioral signal
2282
+ scrollBehavior: 0.15,
2283
+ // Unchanged
2284
+ keyboardTiming: 0.1,
2285
+ // Slightly reduced
2286
+ tabActivity: 0.05,
2287
+ // Unchanged
2288
+ resizeBehavior: 0.02,
2289
+ // Reduced - less reliable
2290
+ environmentFingerprint: 0.08
2291
+ // Reduced - static signal, one-time check
2292
+ },
2293
+ customScorers: {},
2294
+ clickMouseHistoryWindow: 1e3,
2295
+ useWebWorker: false
2296
+ // Will implement Web Worker support in phase 2
2297
+ };
2298
+ // Annotate the CommonJS export names for ESM import in node:
2299
+ 0 && (module.exports = {
2300
+ BehaviorDetector,
2301
+ Click,
2302
+ ClickStrategy,
2303
+ DEFAULT_SETTINGS,
2304
+ Environment,
2305
+ EnvironmentStrategy,
2306
+ Keyboard,
2307
+ KeyboardStrategy,
2308
+ Mouse,
2309
+ MouseStrategy,
2310
+ Resize,
2311
+ ResizeStrategy,
2312
+ Scroll,
2313
+ ScrollStrategy,
2314
+ Tap,
2315
+ TapStrategy,
2316
+ Timing,
2317
+ TimingStrategy,
2318
+ Visibility,
2319
+ VisibilityStrategy
2320
+ });