@axeptio/behavior-detection 1.0.2 → 1.0.3

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.
@@ -0,0 +1,1422 @@
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
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // dist/esm/behavior-detector.js
43
+ var BehaviorDetector = class {
44
+ constructor(tickOptions) {
45
+ this.strategies = /* @__PURE__ */ new Map();
46
+ this.isTracking = false;
47
+ this.isPausedByVisibility = false;
48
+ this.tickInterval = null;
49
+ this.tickIntervalMs = 1e3;
50
+ this.pauseOnHidden = true;
51
+ this.visibilityChangeHandler = null;
52
+ this.confidenceScore = 0;
53
+ this.CONFIDENCE_TARGET = 1;
54
+ this.CONFIDENCE_DECAY = 0.95;
55
+ if (tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.interval) {
56
+ this.tickIntervalMs = tickOptions.interval;
57
+ }
58
+ if ((tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.pauseOnHidden) !== void 0) {
59
+ this.pauseOnHidden = tickOptions.pauseOnHidden;
60
+ }
61
+ }
62
+ /**
63
+ * Add a detection strategy
64
+ */
65
+ addStrategy(strategy, weight) {
66
+ const config = {
67
+ strategy,
68
+ weight: weight !== null && weight !== void 0 ? weight : strategy.defaultWeight,
69
+ enabled: true
70
+ };
71
+ this.strategies.set(strategy.name, config);
72
+ if (strategy.setEventCallback) {
73
+ strategy.setEventCallback((event) => {
74
+ this.onStrategyEvent(event, config.weight);
75
+ });
76
+ }
77
+ if (this.isTracking) {
78
+ strategy.start();
79
+ }
80
+ return this;
81
+ }
82
+ /**
83
+ * Handle event from strategy - update confidence
84
+ */
85
+ onStrategyEvent(event, strategyWeight) {
86
+ this.confidenceScore *= this.CONFIDENCE_DECAY;
87
+ const contribution = event.weight * strategyWeight;
88
+ this.confidenceScore = Math.min(this.CONFIDENCE_TARGET, this.confidenceScore + contribution);
89
+ }
90
+ /**
91
+ * Remove a detection strategy
92
+ */
93
+ removeStrategy(name) {
94
+ const config = this.strategies.get(name);
95
+ if (config) {
96
+ if (this.isTracking) {
97
+ config.strategy.stop();
98
+ }
99
+ this.strategies.delete(name);
100
+ }
101
+ return this;
102
+ }
103
+ /**
104
+ * Enable/disable a strategy
105
+ */
106
+ setStrategyEnabled(name, enabled) {
107
+ const config = this.strategies.get(name);
108
+ if (config) {
109
+ config.enabled = enabled;
110
+ if (!enabled && this.isTracking) {
111
+ config.strategy.stop();
112
+ }
113
+ if (enabled && this.isTracking) {
114
+ config.strategy.start();
115
+ }
116
+ }
117
+ return this;
118
+ }
119
+ /**
120
+ * Get all registered strategies
121
+ */
122
+ getStrategies() {
123
+ return this.strategies;
124
+ }
125
+ /**
126
+ * Start tracking
127
+ */
128
+ start() {
129
+ if (this.isTracking)
130
+ return;
131
+ this.isTracking = true;
132
+ if (this.pauseOnHidden && typeof document !== "undefined") {
133
+ this.visibilityChangeHandler = this.handleVisibilityChange.bind(this);
134
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
135
+ if (document.hidden) {
136
+ this.isPausedByVisibility = true;
137
+ return;
138
+ }
139
+ }
140
+ for (const [_, config] of this.strategies) {
141
+ if (config.enabled) {
142
+ config.strategy.start();
143
+ }
144
+ }
145
+ this.startTick();
146
+ }
147
+ /**
148
+ * Stop tracking
149
+ */
150
+ stop() {
151
+ if (!this.isTracking)
152
+ return;
153
+ this.isTracking = false;
154
+ this.isPausedByVisibility = false;
155
+ if (this.visibilityChangeHandler && typeof document !== "undefined") {
156
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
157
+ this.visibilityChangeHandler = null;
158
+ }
159
+ for (const [_, config] of this.strategies) {
160
+ config.strategy.stop();
161
+ }
162
+ this.stopTick();
163
+ }
164
+ /**
165
+ * Reset all data
166
+ */
167
+ reset() {
168
+ this.confidenceScore = 0;
169
+ for (const [_, config] of this.strategies) {
170
+ config.strategy.reset();
171
+ }
172
+ }
173
+ /**
174
+ * Calculate human likelihood score
175
+ */
176
+ async score(options = {}) {
177
+ const breakdown = this.calculateStrategyScore();
178
+ const result = {
179
+ score: breakdown.overall
180
+ };
181
+ if (options.breakdown) {
182
+ result.breakdown = breakdown;
183
+ }
184
+ return result;
185
+ }
186
+ /**
187
+ * Check if currently tracking
188
+ */
189
+ isActive() {
190
+ return this.isTracking;
191
+ }
192
+ /**
193
+ * Check if currently paused due to tab visibility
194
+ */
195
+ isPaused() {
196
+ return this.isPausedByVisibility;
197
+ }
198
+ /**
199
+ * Get event count from all strategies
200
+ */
201
+ getEventCount() {
202
+ var _a, _b;
203
+ const counts = {};
204
+ for (const [name, config] of this.strategies) {
205
+ const debug = (_b = (_a = config.strategy).getDebugInfo) === null || _b === void 0 ? void 0 : _b.call(_a);
206
+ if ((debug === null || debug === void 0 ? void 0 : debug.eventCount) !== void 0) {
207
+ counts[name] = debug.eventCount;
208
+ }
209
+ }
210
+ return counts;
211
+ }
212
+ /**
213
+ * Get debug info from all strategies
214
+ */
215
+ getStrategyDebugInfo() {
216
+ const debug = {};
217
+ for (const [name, config] of this.strategies) {
218
+ if (config.strategy.getDebugInfo) {
219
+ debug[name] = config.strategy.getDebugInfo();
220
+ }
221
+ }
222
+ return debug;
223
+ }
224
+ /**
225
+ * Get current confidence score (0-1)
226
+ * Represents how much interaction data we've collected
227
+ * Higher confidence = more reliable classification
228
+ */
229
+ getConfidence() {
230
+ return Math.min(1, this.confidenceScore);
231
+ }
232
+ /**
233
+ * Check if confidence is above threshold for reliable classification
234
+ * @param threshold - Minimum confidence (0-1), default 0.3
235
+ */
236
+ hasConfidentData(threshold = 0.3) {
237
+ return this.getConfidence() >= threshold;
238
+ }
239
+ /**
240
+ * Cleanup resources
241
+ */
242
+ destroy() {
243
+ this.stop();
244
+ this.strategies.clear();
245
+ }
246
+ /**
247
+ * Handle visibility change events
248
+ */
249
+ handleVisibilityChange() {
250
+ if (!this.isTracking)
251
+ return;
252
+ if (document.hidden) {
253
+ if (!this.isPausedByVisibility) {
254
+ this.isPausedByVisibility = true;
255
+ for (const [_, config] of this.strategies) {
256
+ if (config.enabled) {
257
+ config.strategy.stop();
258
+ }
259
+ }
260
+ this.stopTick();
261
+ }
262
+ } else {
263
+ if (this.isPausedByVisibility) {
264
+ this.isPausedByVisibility = false;
265
+ for (const [_, config] of this.strategies) {
266
+ if (config.enabled) {
267
+ config.strategy.start();
268
+ }
269
+ }
270
+ this.startTick();
271
+ }
272
+ }
273
+ }
274
+ /**
275
+ * Start tick mechanism for strategies
276
+ */
277
+ startTick() {
278
+ if (this.tickInterval !== null)
279
+ return;
280
+ this.tickInterval = window.setInterval(() => {
281
+ const now = Date.now();
282
+ for (const [_, config] of this.strategies) {
283
+ if (config.enabled && config.strategy.onTick) {
284
+ config.strategy.onTick(now);
285
+ }
286
+ }
287
+ }, this.tickIntervalMs);
288
+ }
289
+ /**
290
+ * Stop tick mechanism
291
+ */
292
+ stopTick() {
293
+ if (this.tickInterval !== null) {
294
+ clearInterval(this.tickInterval);
295
+ this.tickInterval = null;
296
+ }
297
+ }
298
+ /**
299
+ * Calculate score using strategies
300
+ */
301
+ calculateStrategyScore() {
302
+ const factors = {};
303
+ const weights = {};
304
+ let totalWeight = 0;
305
+ let weightedSum = 0;
306
+ for (const [name, config] of this.strategies) {
307
+ if (!config.enabled)
308
+ continue;
309
+ const score = config.strategy.score();
310
+ factors[name] = score;
311
+ weights[name] = config.weight;
312
+ if (score !== void 0 && score !== null) {
313
+ totalWeight += config.weight;
314
+ weightedSum += score * config.weight;
315
+ }
316
+ }
317
+ const overall = totalWeight > 0 ? weightedSum / totalWeight : 0.5;
318
+ return {
319
+ overall: Math.max(0, Math.min(1, overall)),
320
+ factors,
321
+ weights
322
+ };
323
+ }
324
+ };
325
+
326
+ // dist/esm/strategy.js
327
+ var BaseStrategy = class {
328
+ constructor() {
329
+ this.eventCallback = null;
330
+ }
331
+ setEventCallback(callback) {
332
+ this.eventCallback = callback;
333
+ }
334
+ notifyEvent(weight) {
335
+ if (this.eventCallback) {
336
+ this.eventCallback({
337
+ strategy: this.name,
338
+ weight,
339
+ timestamp: Date.now()
340
+ });
341
+ }
342
+ }
343
+ };
344
+
345
+ // dist/esm/math-utils.js
346
+ function sigmoid(x, midpoint = 0, steepness = 1) {
347
+ return 1 / (1 + Math.exp(-steepness * (x - midpoint)));
348
+ }
349
+ function inverseSigmoid(x, midpoint = 0, steepness = 1) {
350
+ return 1 - sigmoid(x, midpoint, steepness);
351
+ }
352
+ function gaussian(x, ideal = 0, width = 1) {
353
+ const exponent = -Math.pow(x - ideal, 2) / (2 * width * width);
354
+ return Math.exp(exponent);
355
+ }
356
+ function calculateStatistics(values) {
357
+ if (values.length === 0) {
358
+ return { mean: 0, variance: 0, stdDev: 0, cv: 0 };
359
+ }
360
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
361
+ const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
362
+ const stdDev = Math.sqrt(variance);
363
+ const cv = mean > 0 ? stdDev / mean : 0;
364
+ return { mean, variance, stdDev, cv };
365
+ }
366
+ function analyzeIntervals(intervals) {
367
+ if (intervals.length < 2)
368
+ return void 0;
369
+ const uniqueCount = new Set(intervals.map((i) => Math.round(i / 10))).size;
370
+ const allIdentical = uniqueCount === 1;
371
+ const statistics = calculateStatistics(intervals);
372
+ return {
373
+ statistics,
374
+ uniqueCount,
375
+ allIdentical
376
+ };
377
+ }
378
+ function scoreCoefficientOfVariation(cv) {
379
+ if (cv < 0.05)
380
+ return 0.1;
381
+ if (cv > 2)
382
+ return 0.3;
383
+ return gaussian(cv, 0.45, 0.35);
384
+ }
385
+
386
+ // dist/esm/strategies/mouse.js
387
+ var MouseStrategy = class extends BaseStrategy {
388
+ constructor(options) {
389
+ super();
390
+ this.name = "mouse";
391
+ this.defaultWeight = 0.3;
392
+ this.distanceSeries = [];
393
+ this.angleSeries = [];
394
+ this.lastPosition = null;
395
+ this.lastAngle = 0;
396
+ this.cumulativeAngle = 0;
397
+ this.rollingWindowMs = 5e3;
398
+ this.listener = null;
399
+ this.leaveListener = null;
400
+ this.isActive = false;
401
+ this.screenDiagonal = 1;
402
+ if ((options === null || options === void 0 ? void 0 : options.rollingWindow) !== void 0)
403
+ this.rollingWindowMs = options.rollingWindow;
404
+ }
405
+ start() {
406
+ if (this.isActive)
407
+ return;
408
+ this.isActive = true;
409
+ const width = window.innerWidth;
410
+ const height = window.innerHeight;
411
+ this.screenDiagonal = Math.sqrt(width * width + height * height);
412
+ this.listener = (e) => {
413
+ const mouseEvent = e;
414
+ const now = Date.now();
415
+ const currentPos = { x: mouseEvent.clientX, y: mouseEvent.clientY };
416
+ if (this.lastPosition) {
417
+ const dx = currentPos.x - this.lastPosition.x;
418
+ const dy = currentPos.y - this.lastPosition.y;
419
+ const pixelDistance = Math.sqrt(dx * dx + dy * dy);
420
+ const normalizedDistance = pixelDistance / this.screenDiagonal;
421
+ if (normalizedDistance > 1e-3) {
422
+ const rawAngle = Math.atan2(dy, dx);
423
+ let angleDiff = rawAngle - this.lastAngle;
424
+ while (angleDiff > Math.PI)
425
+ angleDiff -= 2 * Math.PI;
426
+ while (angleDiff < -Math.PI)
427
+ angleDiff += 2 * Math.PI;
428
+ this.cumulativeAngle += angleDiff;
429
+ this.lastAngle = rawAngle;
430
+ this.distanceSeries.push({ value: normalizedDistance, timestamp: now });
431
+ this.angleSeries.push({ value: this.cumulativeAngle, timestamp: now });
432
+ this.notifyEvent(Math.min(1, normalizedDistance * 100));
433
+ }
434
+ const cutoff = now - this.rollingWindowMs;
435
+ while (this.distanceSeries.length > 0 && this.distanceSeries[0].timestamp < cutoff) {
436
+ this.distanceSeries.shift();
437
+ this.angleSeries.shift();
438
+ }
439
+ }
440
+ this.lastPosition = currentPos;
441
+ };
442
+ document.addEventListener("mousemove", this.listener, { passive: true });
443
+ this.leaveListener = () => {
444
+ this.distanceSeries = [];
445
+ this.angleSeries = [];
446
+ this.lastPosition = null;
447
+ this.lastAngle = 0;
448
+ this.cumulativeAngle = 0;
449
+ };
450
+ document.addEventListener("mouseleave", this.leaveListener, { passive: true });
451
+ }
452
+ stop() {
453
+ if (!this.isActive)
454
+ return;
455
+ this.isActive = false;
456
+ if (this.listener) {
457
+ document.removeEventListener("mousemove", this.listener);
458
+ this.listener = null;
459
+ }
460
+ if (this.leaveListener) {
461
+ document.removeEventListener("mouseleave", this.leaveListener);
462
+ this.leaveListener = null;
463
+ }
464
+ }
465
+ reset() {
466
+ this.distanceSeries = [];
467
+ this.angleSeries = [];
468
+ this.lastPosition = null;
469
+ this.lastAngle = 0;
470
+ this.cumulativeAngle = 0;
471
+ }
472
+ score() {
473
+ if (this.distanceSeries.length < 10)
474
+ return void 0;
475
+ return this.detectMousePatterns();
476
+ }
477
+ /**
478
+ * Mouse-specific pattern detection
479
+ * Detects bot-like patterns: constant velocity, linear paths
480
+ */
481
+ detectMousePatterns() {
482
+ if (this.distanceSeries.length < 10)
483
+ return void 0;
484
+ let score = 0;
485
+ let factors = 0;
486
+ const distances = this.distanceSeries.map((p) => p.value);
487
+ if (distances.length >= 3) {
488
+ const stats = calculateStatistics(distances);
489
+ score += gaussian(stats.cv, 0.9, 0.35);
490
+ factors++;
491
+ }
492
+ const angles = this.angleSeries.map((p) => p.value);
493
+ if (angles.length >= 3) {
494
+ const angleChanges = [];
495
+ for (let i = 1; i < angles.length; i++) {
496
+ angleChanges.push(Math.abs(angles[i] - angles[i - 1]));
497
+ }
498
+ const avgChange = angleChanges.reduce((a, b) => a + b, 0) / angleChanges.length;
499
+ score += gaussian(avgChange, 0.15, 0.12);
500
+ factors++;
501
+ }
502
+ return factors > 0 ? score / factors : void 0;
503
+ }
504
+ getDebugInfo() {
505
+ return {
506
+ eventCount: this.distanceSeries.length,
507
+ rollingWindow: this.rollingWindowMs,
508
+ isActive: this.isActive,
509
+ distanceSeries: this.distanceSeries,
510
+ angleSeries: this.angleSeries
511
+ };
512
+ }
513
+ };
514
+
515
+ // dist/esm/strategies/scroll.js
516
+ var ScrollStrategy = class extends BaseStrategy {
517
+ constructor(options) {
518
+ super();
519
+ this.name = "scroll";
520
+ this.defaultWeight = 0.15;
521
+ this.distanceSeries = [];
522
+ this.velocitySeries = [];
523
+ this.lastScrollY = null;
524
+ this.lastTimestamp = 0;
525
+ this.rollingWindowMs = 5e3;
526
+ this.documentHeight = 1;
527
+ this.listener = null;
528
+ this.isActive = false;
529
+ if ((options === null || options === void 0 ? void 0 : options.rollingWindow) !== void 0)
530
+ this.rollingWindowMs = options.rollingWindow;
531
+ }
532
+ start() {
533
+ if (this.isActive)
534
+ return;
535
+ this.isActive = true;
536
+ this.documentHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, 1);
537
+ this.listener = (_e) => {
538
+ const scrollY = window.scrollY;
539
+ const now = Date.now();
540
+ if (this.lastScrollY === null) {
541
+ this.lastScrollY = scrollY;
542
+ this.lastTimestamp = now;
543
+ return;
544
+ }
545
+ const pixelDelta = scrollY - this.lastScrollY;
546
+ const deltaTime = now - this.lastTimestamp;
547
+ if (pixelDelta === 0) {
548
+ return;
549
+ }
550
+ const normalizedDistance = Math.abs(pixelDelta) / this.documentHeight;
551
+ if (deltaTime > 0) {
552
+ const velocity = normalizedDistance / deltaTime;
553
+ this.distanceSeries.push({ value: normalizedDistance, timestamp: now });
554
+ this.velocitySeries.push({ value: velocity, timestamp: now });
555
+ this.notifyEvent(Math.min(1, normalizedDistance * 10));
556
+ const cutoff = now - this.rollingWindowMs;
557
+ while (this.distanceSeries.length > 0 && this.distanceSeries[0].timestamp < cutoff) {
558
+ this.distanceSeries.shift();
559
+ this.velocitySeries.shift();
560
+ }
561
+ }
562
+ this.lastScrollY = scrollY;
563
+ this.lastTimestamp = now;
564
+ };
565
+ window.addEventListener("scroll", this.listener, { passive: true });
566
+ }
567
+ stop() {
568
+ if (!this.isActive)
569
+ return;
570
+ this.isActive = false;
571
+ if (this.listener) {
572
+ window.removeEventListener("scroll", this.listener);
573
+ this.listener = null;
574
+ }
575
+ }
576
+ reset() {
577
+ this.distanceSeries = [];
578
+ this.velocitySeries = [];
579
+ this.lastScrollY = null;
580
+ this.lastTimestamp = 0;
581
+ }
582
+ score() {
583
+ if (this.distanceSeries.length < 2)
584
+ return void 0;
585
+ return this.detectScrollPatterns();
586
+ }
587
+ /**
588
+ * Scroll-specific pattern detection
589
+ * Detects bot-like patterns: constant deltas, instant jumps, too smooth
590
+ */
591
+ detectScrollPatterns() {
592
+ if (this.distanceSeries.length < 2)
593
+ return void 0;
594
+ let score = 0;
595
+ let factors = 0;
596
+ const distances = this.distanceSeries.map((p) => p.value);
597
+ if (distances.length >= 2) {
598
+ const uniqueDistances = new Set(distances.map((d) => Math.round(d * 1e3))).size;
599
+ if (uniqueDistances === 1) {
600
+ return 0.05;
601
+ }
602
+ }
603
+ if (distances.length >= 2) {
604
+ const stats = calculateStatistics(distances);
605
+ score += gaussian(stats.cv, 1, 0.4);
606
+ factors++;
607
+ }
608
+ const velocities = this.velocitySeries.map((p) => p.value);
609
+ if (velocities.length >= 2) {
610
+ const stats = calculateStatistics(velocities);
611
+ score += gaussian(stats.cv, 1.2, 0.5);
612
+ factors++;
613
+ }
614
+ const instantJumps = distances.filter((d) => d > 0.1).length;
615
+ if (distances.length > 0) {
616
+ const jumpRatio = instantJumps / distances.length;
617
+ score += inverseSigmoid(jumpRatio, 0.3, 15);
618
+ factors++;
619
+ }
620
+ return factors > 0 ? score / factors : void 0;
621
+ }
622
+ getDebugInfo() {
623
+ return {
624
+ eventCount: this.distanceSeries.length,
625
+ rollingWindow: this.rollingWindowMs,
626
+ isActive: this.isActive,
627
+ distanceSeries: this.distanceSeries,
628
+ velocitySeries: this.velocitySeries
629
+ };
630
+ }
631
+ };
632
+
633
+ // dist/esm/strategies/click.js
634
+ var ClickStrategy = class extends BaseStrategy {
635
+ constructor(options) {
636
+ super();
637
+ this.name = "click";
638
+ this.defaultWeight = 0.3;
639
+ this.events = [];
640
+ this.targetSelectors = ["button", "a", 'input[type="submit"]', '[role="button"]'];
641
+ this.lastMousePosition = null;
642
+ this.mouseListener = null;
643
+ this.clickListeners = /* @__PURE__ */ new Map();
644
+ this.isActive = false;
645
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
646
+ this.targetSelectors = options.targetSelectors;
647
+ }
648
+ }
649
+ start() {
650
+ if (this.isActive)
651
+ return;
652
+ this.isActive = true;
653
+ this.mouseListener = (e) => {
654
+ const mouseEvent = e;
655
+ this.lastMousePosition = {
656
+ x: mouseEvent.clientX,
657
+ y: mouseEvent.clientY
658
+ };
659
+ };
660
+ document.addEventListener("mousemove", this.mouseListener, { passive: true });
661
+ this.attachClickListeners();
662
+ }
663
+ /**
664
+ * Add a new target selector at runtime
665
+ */
666
+ addTarget(selector) {
667
+ if (!this.targetSelectors.includes(selector)) {
668
+ this.targetSelectors.push(selector);
669
+ if (this.isActive) {
670
+ this.attachClickListenersForSelector(selector);
671
+ }
672
+ }
673
+ }
674
+ attachClickListeners() {
675
+ this.targetSelectors.forEach((selector) => {
676
+ this.attachClickListenersForSelector(selector);
677
+ });
678
+ }
679
+ attachClickListenersForSelector(selector) {
680
+ const elements = document.querySelectorAll(selector);
681
+ elements.forEach((element) => {
682
+ if (this.clickListeners.has(element))
683
+ return;
684
+ const listener = (e) => {
685
+ var _a, _b;
686
+ const clickEvent = e;
687
+ const rect = element.getBoundingClientRect();
688
+ const inViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
689
+ let position;
690
+ if (!this.lastMousePosition) {
691
+ position = "no-mouse-data";
692
+ } else {
693
+ const mx = this.lastMousePosition.x;
694
+ const my = this.lastMousePosition.y;
695
+ const overElement = mx >= rect.left && mx <= rect.right && my >= rect.top && my <= rect.bottom;
696
+ if (!overElement) {
697
+ position = "outside";
698
+ } else {
699
+ const centerX = rect.left + rect.width / 2;
700
+ const centerY = rect.top + rect.height / 2;
701
+ const distanceFromCenter = Math.sqrt((mx - centerX) ** 2 + (my - centerY) ** 2);
702
+ if (distanceFromCenter < 2) {
703
+ position = "dead-center";
704
+ } else {
705
+ position = "over-element";
706
+ }
707
+ }
708
+ }
709
+ this.events.push({
710
+ clickX: clickEvent.clientX,
711
+ clickY: clickEvent.clientY,
712
+ mouseX: (_a = this.lastMousePosition) === null || _a === void 0 ? void 0 : _a.x,
713
+ mouseY: (_b = this.lastMousePosition) === null || _b === void 0 ? void 0 : _b.y,
714
+ rect,
715
+ inViewport,
716
+ position,
717
+ timestamp: Date.now()
718
+ });
719
+ this.notifyEvent(1);
720
+ };
721
+ element.addEventListener("click", listener);
722
+ this.clickListeners.set(element, listener);
723
+ });
724
+ }
725
+ stop() {
726
+ if (!this.isActive)
727
+ return;
728
+ this.isActive = false;
729
+ if (this.mouseListener) {
730
+ document.removeEventListener("mousemove", this.mouseListener);
731
+ this.mouseListener = null;
732
+ }
733
+ this.clickListeners.forEach((listener, element) => {
734
+ element.removeEventListener("click", listener);
735
+ });
736
+ this.clickListeners.clear();
737
+ }
738
+ reset() {
739
+ this.events = [];
740
+ this.lastMousePosition = null;
741
+ }
742
+ score() {
743
+ if (this.events.length === 0)
744
+ return void 0;
745
+ let totalScore = 0;
746
+ for (const click of this.events) {
747
+ switch (click.position) {
748
+ case "no-mouse-data":
749
+ totalScore += 0;
750
+ break;
751
+ case "outside":
752
+ totalScore += 0;
753
+ break;
754
+ case "dead-center":
755
+ totalScore += 0.5;
756
+ break;
757
+ case "over-element":
758
+ totalScore += 1;
759
+ break;
760
+ }
761
+ }
762
+ return totalScore / this.events.length;
763
+ }
764
+ getDebugInfo() {
765
+ return {
766
+ eventCount: this.events.length,
767
+ positions: {
768
+ noMouseData: this.events.filter((e) => e.position === "no-mouse-data").length,
769
+ outside: this.events.filter((e) => e.position === "outside").length,
770
+ deadCenter: this.events.filter((e) => e.position === "dead-center").length,
771
+ overElement: this.events.filter((e) => e.position === "over-element").length
772
+ },
773
+ inViewport: this.events.filter((e) => e.inViewport).length,
774
+ trackedElements: this.clickListeners.size
775
+ };
776
+ }
777
+ };
778
+
779
+ // dist/esm/strategies/tap.js
780
+ var TapStrategy = class extends BaseStrategy {
781
+ constructor(options) {
782
+ super();
783
+ this.name = "tap";
784
+ this.defaultWeight = 0.35;
785
+ this.events = [];
786
+ this.targetSelectors = ["button", "a", 'input[type="submit"]', '[role="button"]'];
787
+ this.touchStartListeners = /* @__PURE__ */ new Map();
788
+ this.touchEndListeners = /* @__PURE__ */ new Map();
789
+ this.activeTouches = /* @__PURE__ */ new Map();
790
+ this.isActive = false;
791
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
792
+ this.targetSelectors = options.targetSelectors;
793
+ }
794
+ }
795
+ start() {
796
+ if (this.isActive)
797
+ return;
798
+ this.isActive = true;
799
+ this.attachTouchListeners();
800
+ }
801
+ addTarget(selector) {
802
+ if (!this.targetSelectors.includes(selector)) {
803
+ this.targetSelectors.push(selector);
804
+ if (this.isActive) {
805
+ this.attachTouchListenersForSelector(selector);
806
+ }
807
+ }
808
+ }
809
+ attachTouchListeners() {
810
+ this.targetSelectors.forEach((selector) => {
811
+ this.attachTouchListenersForSelector(selector);
812
+ });
813
+ }
814
+ attachTouchListenersForSelector(selector) {
815
+ const elements = document.querySelectorAll(selector);
816
+ elements.forEach((element) => {
817
+ if (this.touchStartListeners.has(element))
818
+ return;
819
+ const startListener = (e) => {
820
+ const touchEvent = e;
821
+ if (touchEvent.touches.length !== 1)
822
+ return;
823
+ const touch = touchEvent.touches[0];
824
+ this.activeTouches.set(touch.identifier, {
825
+ x: touch.clientX,
826
+ y: touch.clientY,
827
+ timestamp: Date.now(),
828
+ element
829
+ });
830
+ };
831
+ const endListener = (e) => {
832
+ const touchEvent = e;
833
+ if (touchEvent.changedTouches.length !== 1)
834
+ return;
835
+ const touch = touchEvent.changedTouches[0];
836
+ const startData = this.activeTouches.get(touch.identifier);
837
+ if (!startData || startData.element !== element)
838
+ return;
839
+ const now = Date.now();
840
+ const duration = now - startData.timestamp;
841
+ const dx = touch.clientX - startData.x;
842
+ const dy = touch.clientY - startData.y;
843
+ const movement = Math.sqrt(dx * dx + dy * dy);
844
+ const rect = element.getBoundingClientRect();
845
+ const inViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
846
+ const tx = touch.clientX;
847
+ const ty = touch.clientY;
848
+ const overElement = tx >= rect.left && tx <= rect.right && ty >= rect.top && ty <= rect.bottom;
849
+ let position;
850
+ if (!overElement) {
851
+ position = "outside";
852
+ } else {
853
+ const centerX = rect.left + rect.width / 2;
854
+ const centerY = rect.top + rect.height / 2;
855
+ const distanceFromCenter = Math.sqrt((tx - centerX) ** 2 + (ty - centerY) ** 2);
856
+ position = distanceFromCenter < 2 ? "dead-center" : "over-element";
857
+ }
858
+ this.events.push({
859
+ x: tx,
860
+ y: ty,
861
+ rect,
862
+ inViewport,
863
+ position,
864
+ duration,
865
+ movement,
866
+ timestamp: now
867
+ });
868
+ this.notifyEvent(1);
869
+ this.activeTouches.delete(touch.identifier);
870
+ };
871
+ element.addEventListener("touchstart", startListener, { passive: true });
872
+ element.addEventListener("touchend", endListener, { passive: true });
873
+ this.touchStartListeners.set(element, startListener);
874
+ this.touchEndListeners.set(element, endListener);
875
+ });
876
+ }
877
+ stop() {
878
+ if (!this.isActive)
879
+ return;
880
+ this.isActive = false;
881
+ this.touchStartListeners.forEach((listener, element) => {
882
+ element.removeEventListener("touchstart", listener);
883
+ });
884
+ this.touchStartListeners.clear();
885
+ this.touchEndListeners.forEach((listener, element) => {
886
+ element.removeEventListener("touchend", listener);
887
+ });
888
+ this.touchEndListeners.clear();
889
+ this.activeTouches.clear();
890
+ }
891
+ reset() {
892
+ this.events = [];
893
+ this.activeTouches.clear();
894
+ }
895
+ score() {
896
+ if (this.events.length === 0)
897
+ return void 0;
898
+ let score = 0;
899
+ let factors = 0;
900
+ let positionScore = 0;
901
+ for (const tap of this.events) {
902
+ switch (tap.position) {
903
+ case "outside":
904
+ positionScore += 0;
905
+ break;
906
+ case "dead-center":
907
+ positionScore += 0.5;
908
+ break;
909
+ case "over-element":
910
+ positionScore += 1;
911
+ break;
912
+ }
913
+ }
914
+ score += positionScore / this.events.length;
915
+ factors++;
916
+ const durations = this.events.map((e) => e.duration);
917
+ if (durations.length >= 2) {
918
+ const durationAnalysis = analyzeIntervals(durations);
919
+ if (durationAnalysis) {
920
+ const { statistics, allIdentical } = durationAnalysis;
921
+ if (allIdentical) {
922
+ return 0.05;
923
+ }
924
+ score += scoreCoefficientOfVariation(statistics.cv);
925
+ factors++;
926
+ score += gaussian(statistics.mean, 95, 40);
927
+ factors++;
928
+ }
929
+ }
930
+ const movements = this.events.map((e) => e.movement);
931
+ if (movements.length > 0) {
932
+ const avgMovement = movements.reduce((a, b) => a + b, 0) / movements.length;
933
+ score += gaussian(avgMovement, 2, 3);
934
+ factors++;
935
+ }
936
+ if (this.events.length >= 3) {
937
+ const intervals = [];
938
+ for (let i = 1; i < this.events.length; i++) {
939
+ intervals.push(this.events[i].timestamp - this.events[i - 1].timestamp);
940
+ }
941
+ const intervalAnalysis = analyzeIntervals(intervals);
942
+ if (intervalAnalysis && !intervalAnalysis.allIdentical) {
943
+ score += scoreCoefficientOfVariation(intervalAnalysis.statistics.cv);
944
+ factors++;
945
+ }
946
+ }
947
+ return factors > 0 ? score / factors : void 0;
948
+ }
949
+ getDebugInfo() {
950
+ return {
951
+ eventCount: this.events.length,
952
+ positions: {
953
+ outside: this.events.filter((e) => e.position === "outside").length,
954
+ deadCenter: this.events.filter((e) => e.position === "dead-center").length,
955
+ overElement: this.events.filter((e) => e.position === "over-element").length
956
+ },
957
+ durations: this.events.map((e) => e.duration),
958
+ movements: this.events.map((e) => e.movement),
959
+ inViewport: this.events.filter((e) => e.inViewport).length,
960
+ trackedElements: this.touchStartListeners.size
961
+ };
962
+ }
963
+ };
964
+
965
+ // dist/esm/strategies/keyboard.js
966
+ var KeyboardStrategy = class extends BaseStrategy {
967
+ constructor(options) {
968
+ super();
969
+ this.name = "keyboard";
970
+ this.defaultWeight = 0.1;
971
+ this.events = [];
972
+ this.targetSelectors = ['input[type="text"]', 'input[type="email"]', "textarea"];
973
+ this.focusedElement = null;
974
+ this.focusedElementSelector = "";
975
+ this.lastEventTimestamp = 0;
976
+ this.sessionPauseThreshold = 1e3;
977
+ this.downListener = null;
978
+ this.upListener = null;
979
+ this.focusListeners = /* @__PURE__ */ new Map();
980
+ this.blurListeners = /* @__PURE__ */ new Map();
981
+ this.isActive = false;
982
+ if (options === null || options === void 0 ? void 0 : options.targetSelectors) {
983
+ this.targetSelectors = options.targetSelectors;
984
+ }
985
+ }
986
+ start() {
987
+ if (this.isActive)
988
+ return;
989
+ this.isActive = true;
990
+ this.attachFocusListeners();
991
+ this.downListener = (e) => {
992
+ if (!this.focusedElement)
993
+ return;
994
+ const now = Date.now();
995
+ const keyEvent = e;
996
+ if (this.lastEventTimestamp > 0 && now - this.lastEventTimestamp > this.sessionPauseThreshold) {
997
+ this.events = [];
998
+ }
999
+ this.events.push({
1000
+ key: keyEvent.key,
1001
+ type: "down",
1002
+ timestamp: now,
1003
+ targetElement: this.focusedElementSelector
1004
+ });
1005
+ this.notifyEvent(0.8);
1006
+ this.lastEventTimestamp = now;
1007
+ };
1008
+ this.upListener = (e) => {
1009
+ if (!this.focusedElement)
1010
+ return;
1011
+ const now = Date.now();
1012
+ const keyEvent = e;
1013
+ if (this.lastEventTimestamp > 0 && now - this.lastEventTimestamp > this.sessionPauseThreshold) {
1014
+ this.events = [];
1015
+ }
1016
+ this.events.push({
1017
+ key: keyEvent.key,
1018
+ type: "up",
1019
+ timestamp: now,
1020
+ targetElement: this.focusedElementSelector
1021
+ });
1022
+ this.lastEventTimestamp = now;
1023
+ };
1024
+ document.addEventListener("keydown", this.downListener);
1025
+ document.addEventListener("keyup", this.upListener);
1026
+ }
1027
+ /**
1028
+ * Add a new target selector at runtime
1029
+ */
1030
+ addTarget(selector) {
1031
+ if (!this.targetSelectors.includes(selector)) {
1032
+ this.targetSelectors.push(selector);
1033
+ if (this.isActive) {
1034
+ this.attachFocusListenersForSelector(selector);
1035
+ }
1036
+ }
1037
+ }
1038
+ attachFocusListeners() {
1039
+ this.targetSelectors.forEach((selector) => {
1040
+ this.attachFocusListenersForSelector(selector);
1041
+ });
1042
+ }
1043
+ attachFocusListenersForSelector(selector) {
1044
+ const elements = document.querySelectorAll(selector);
1045
+ elements.forEach((element) => {
1046
+ if (this.focusListeners.has(element))
1047
+ return;
1048
+ const focusListener = () => {
1049
+ this.focusedElement = element;
1050
+ this.focusedElementSelector = selector;
1051
+ };
1052
+ const blurListener = () => {
1053
+ this.focusedElement = null;
1054
+ this.focusedElementSelector = "";
1055
+ };
1056
+ element.addEventListener("focus", focusListener);
1057
+ element.addEventListener("blur", blurListener);
1058
+ this.focusListeners.set(element, focusListener);
1059
+ this.blurListeners.set(element, blurListener);
1060
+ });
1061
+ }
1062
+ stop() {
1063
+ if (!this.isActive)
1064
+ return;
1065
+ this.isActive = false;
1066
+ if (this.downListener) {
1067
+ document.removeEventListener("keydown", this.downListener);
1068
+ this.downListener = null;
1069
+ }
1070
+ if (this.upListener) {
1071
+ document.removeEventListener("keyup", this.upListener);
1072
+ this.upListener = null;
1073
+ }
1074
+ this.focusListeners.forEach((listener, element) => {
1075
+ element.removeEventListener("focus", listener);
1076
+ });
1077
+ this.focusListeners.clear();
1078
+ this.blurListeners.forEach((listener, element) => {
1079
+ element.removeEventListener("blur", listener);
1080
+ });
1081
+ this.blurListeners.clear();
1082
+ this.focusedElement = null;
1083
+ }
1084
+ reset() {
1085
+ this.events = [];
1086
+ this.focusedElement = null;
1087
+ this.focusedElementSelector = "";
1088
+ this.lastEventTimestamp = 0;
1089
+ }
1090
+ score() {
1091
+ if (this.events.length < 6)
1092
+ return void 0;
1093
+ const downEvents = this.events.filter((e) => e.type === "down");
1094
+ if (downEvents.length < 3)
1095
+ return void 0;
1096
+ let score = 0;
1097
+ let factors = 0;
1098
+ const keystrokeIntervals = [];
1099
+ for (let i = 1; i < downEvents.length; i++) {
1100
+ keystrokeIntervals.push(downEvents[i].timestamp - downEvents[i - 1].timestamp);
1101
+ }
1102
+ const keystrokeAnalysis = analyzeIntervals(keystrokeIntervals);
1103
+ if (keystrokeAnalysis) {
1104
+ const { statistics, allIdentical } = keystrokeAnalysis;
1105
+ if (allIdentical) {
1106
+ return 0.1;
1107
+ }
1108
+ const keystrokeScore = scoreCoefficientOfVariation(statistics.cv);
1109
+ score += keystrokeScore;
1110
+ factors++;
1111
+ if (keystrokeScore <= 0.1)
1112
+ return keystrokeScore;
1113
+ }
1114
+ const pressDurations = [];
1115
+ for (let i = 0; i < this.events.length - 1; i++) {
1116
+ if (this.events[i].type === "down" && this.events[i + 1].type === "up" && this.events[i].key === this.events[i + 1].key) {
1117
+ pressDurations.push(this.events[i + 1].timestamp - this.events[i].timestamp);
1118
+ }
1119
+ }
1120
+ const pressDurationAnalysis = analyzeIntervals(pressDurations);
1121
+ if (pressDurationAnalysis) {
1122
+ const { statistics, allIdentical } = pressDurationAnalysis;
1123
+ if (allIdentical) {
1124
+ return 0.1;
1125
+ }
1126
+ if (statistics.mean < 5) {
1127
+ score += 0.1;
1128
+ factors++;
1129
+ } else {
1130
+ const pressDurationScore = scoreCoefficientOfVariation(statistics.cv);
1131
+ score += pressDurationScore;
1132
+ factors++;
1133
+ if (pressDurationScore <= 0.1)
1134
+ return pressDurationScore;
1135
+ }
1136
+ }
1137
+ const backspaceCount = this.events.filter((e) => e.key === "Backspace").length;
1138
+ if (backspaceCount > 0) {
1139
+ const backspaceRatio = backspaceCount / downEvents.length;
1140
+ if (backspaceRatio > 0.05 && backspaceRatio < 0.3) {
1141
+ score += 1;
1142
+ factors++;
1143
+ } else if (backspaceRatio > 0) {
1144
+ score += 0.8;
1145
+ factors++;
1146
+ }
1147
+ }
1148
+ return factors > 0 ? score / factors : void 0;
1149
+ }
1150
+ getDebugInfo() {
1151
+ const pressDurations = [];
1152
+ for (let i = 0; i < this.events.length - 1; i++) {
1153
+ if (this.events[i].type === "down" && this.events[i + 1].type === "up" && this.events[i].key === this.events[i + 1].key) {
1154
+ pressDurations.push(this.events[i + 1].timestamp - this.events[i].timestamp);
1155
+ }
1156
+ }
1157
+ return {
1158
+ eventCount: this.events.length,
1159
+ downEvents: this.events.filter((e) => e.type === "down").length,
1160
+ upEvents: this.events.filter((e) => e.type === "up").length,
1161
+ backspaceCount: this.events.filter((e) => e.key === "Backspace").length,
1162
+ pressDurations,
1163
+ // For graphing
1164
+ focusedElement: this.focusedElementSelector,
1165
+ trackedElements: this.focusListeners.size
1166
+ };
1167
+ }
1168
+ };
1169
+
1170
+ // dist/esm/strategies/environment.js
1171
+ var EnvironmentStrategy = class extends BaseStrategy {
1172
+ constructor() {
1173
+ super(...arguments);
1174
+ this.name = "environment";
1175
+ this.defaultWeight = 0.08;
1176
+ this.data = null;
1177
+ }
1178
+ start() {
1179
+ this.captureEnvironment();
1180
+ }
1181
+ stop() {
1182
+ }
1183
+ reset() {
1184
+ this.data = null;
1185
+ }
1186
+ onTick(_timestamp) {
1187
+ this.captureEnvironment();
1188
+ }
1189
+ score() {
1190
+ if (!this.data)
1191
+ return void 0;
1192
+ const env = this.data;
1193
+ let score = 0;
1194
+ let factors = 0;
1195
+ score += env.suspiciousDimensions ? 0.1 : 1;
1196
+ score += env.suspiciousRatio ? 0.2 : 1;
1197
+ score += env.featureInconsistency ? 0.3 : 1;
1198
+ factors += 3;
1199
+ const featureCount = [
1200
+ env.hasWebGL,
1201
+ env.hasLocalStorage,
1202
+ env.hasSessionStorage,
1203
+ env.hasIndexedDB
1204
+ ].filter(Boolean).length;
1205
+ score += featureCount / 4;
1206
+ factors++;
1207
+ score += inverseSigmoid(env.plugins, -2, -0.5);
1208
+ score += env.plugins > 0 ? 1 : 0.1;
1209
+ factors += 2;
1210
+ score += gaussian(env.devicePixelRatio, 2, 1.5);
1211
+ score += env.colorDepth === 24 || env.colorDepth === 32 ? 1 : 0.4;
1212
+ factors += 2;
1213
+ return factors > 0 ? score / factors : void 0;
1214
+ }
1215
+ isMobileDevice() {
1216
+ const hasTouchScreen = navigator.maxTouchPoints > 0 || "ontouchstart" in window;
1217
+ const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1218
+ const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
1219
+ return hasTouchScreen && smallScreen || mobileUA;
1220
+ }
1221
+ captureEnvironment() {
1222
+ try {
1223
+ let hasWebGL = false;
1224
+ try {
1225
+ const canvas = document.createElement("canvas");
1226
+ hasWebGL = !!(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"));
1227
+ } catch (e) {
1228
+ hasWebGL = false;
1229
+ }
1230
+ const hasWebRTC = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
1231
+ const isMobile = this.isMobileDevice();
1232
+ const windowWidth = window.innerWidth;
1233
+ const windowHeight = window.innerHeight;
1234
+ const screenWidth = window.screen.width;
1235
+ const screenHeight = window.screen.height;
1236
+ const suspiciousDimensions = windowWidth === 800 && windowHeight === 600 || windowWidth === 1024 && windowHeight === 768 || windowWidth === 1280 && windowHeight === 720 || screenWidth === 800 && screenHeight === 600;
1237
+ const windowScreenRatio = windowWidth * windowHeight / (screenWidth * screenHeight);
1238
+ const suspiciousRatio = windowScreenRatio === 1 || windowScreenRatio < 0.1 || windowScreenRatio > 1;
1239
+ const hasStorage = typeof localStorage !== "undefined" && typeof sessionStorage !== "undefined";
1240
+ const featureInconsistency = navigator.plugins.length === 0 && navigator.mimeTypes.length === 0 || !hasWebGL || !hasStorage;
1241
+ this.data = {
1242
+ screenWidth,
1243
+ screenHeight,
1244
+ windowWidth,
1245
+ windowHeight,
1246
+ devicePixelRatio: window.devicePixelRatio,
1247
+ colorDepth: window.screen.colorDepth,
1248
+ userAgent: navigator.userAgent,
1249
+ platform: navigator.platform,
1250
+ language: navigator.language,
1251
+ languages: navigator.languages ? Array.from(navigator.languages) : [navigator.language],
1252
+ hardwareConcurrency: navigator.hardwareConcurrency,
1253
+ maxTouchPoints: navigator.maxTouchPoints || 0,
1254
+ vendor: navigator.vendor,
1255
+ hasWebGL,
1256
+ hasWebRTC,
1257
+ hasLocalStorage: typeof localStorage !== "undefined",
1258
+ hasSessionStorage: typeof sessionStorage !== "undefined",
1259
+ hasIndexedDB: "indexedDB" in window,
1260
+ plugins: navigator.plugins.length,
1261
+ mimeTypes: navigator.mimeTypes.length,
1262
+ suspiciousRatio,
1263
+ suspiciousDimensions,
1264
+ featureInconsistency,
1265
+ isMobile,
1266
+ timestamp: Date.now()
1267
+ };
1268
+ } catch (error) {
1269
+ console.warn("Failed to capture environment:", error);
1270
+ }
1271
+ }
1272
+ getDebugInfo() {
1273
+ return this.data;
1274
+ }
1275
+ /**
1276
+ * Check if current device is mobile
1277
+ * Returns true if mobile, false otherwise
1278
+ * Returns null if environment hasn't been captured yet
1279
+ */
1280
+ isMobile() {
1281
+ var _a, _b;
1282
+ return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.isMobile) !== null && _b !== void 0 ? _b : null;
1283
+ }
1284
+ };
1285
+
1286
+ // dist/esm/strategies/resize.js
1287
+ var ResizeStrategy = class extends BaseStrategy {
1288
+ constructor() {
1289
+ super(...arguments);
1290
+ this.name = "resize";
1291
+ this.defaultWeight = 0.02;
1292
+ this.events = [];
1293
+ this.listener = null;
1294
+ this.isActive = false;
1295
+ this.lastMousePosition = null;
1296
+ this.mouseListener = null;
1297
+ }
1298
+ start() {
1299
+ if (this.isActive)
1300
+ return;
1301
+ this.isActive = true;
1302
+ this.mouseListener = (e) => {
1303
+ const mouseEvent = e;
1304
+ this.lastMousePosition = {
1305
+ x: mouseEvent.clientX,
1306
+ y: mouseEvent.clientY
1307
+ };
1308
+ };
1309
+ document.addEventListener("mousemove", this.mouseListener, { passive: true });
1310
+ this.listener = () => {
1311
+ var _a, _b;
1312
+ const mouseX = (_a = this.lastMousePosition) === null || _a === void 0 ? void 0 : _a.x;
1313
+ const mouseY = (_b = this.lastMousePosition) === null || _b === void 0 ? void 0 : _b.y;
1314
+ let mouseNearEdge = false;
1315
+ if (mouseX !== void 0 && mouseY !== void 0) {
1316
+ const edgeThreshold = 50;
1317
+ mouseNearEdge = mouseX < edgeThreshold || mouseX > window.innerWidth - edgeThreshold || mouseY < edgeThreshold || mouseY > window.innerHeight - edgeThreshold;
1318
+ }
1319
+ this.events.push({
1320
+ width: window.innerWidth,
1321
+ height: window.innerHeight,
1322
+ mouseX,
1323
+ mouseY,
1324
+ mouseNearEdge,
1325
+ timestamp: Date.now()
1326
+ });
1327
+ };
1328
+ window.addEventListener("resize", this.listener);
1329
+ }
1330
+ stop() {
1331
+ if (!this.isActive)
1332
+ return;
1333
+ this.isActive = false;
1334
+ if (this.listener) {
1335
+ window.removeEventListener("resize", this.listener);
1336
+ this.listener = null;
1337
+ }
1338
+ if (this.mouseListener) {
1339
+ document.removeEventListener("mousemove", this.mouseListener);
1340
+ this.mouseListener = null;
1341
+ }
1342
+ }
1343
+ reset() {
1344
+ this.events = [];
1345
+ }
1346
+ score() {
1347
+ if (this.events.length === 0)
1348
+ return void 0;
1349
+ let score = 0;
1350
+ let factors = 0;
1351
+ score += inverseSigmoid(this.events.length, 5, 0.5);
1352
+ factors++;
1353
+ const withMouse = this.events.filter((e) => e.mouseX !== void 0);
1354
+ if (withMouse.length > 0) {
1355
+ const nearEdge = withMouse.filter((e) => e.mouseNearEdge).length;
1356
+ score += sigmoid(nearEdge / withMouse.length, 0.5, 8);
1357
+ factors++;
1358
+ }
1359
+ return factors > 0 ? score / factors : void 0;
1360
+ }
1361
+ getDebugInfo() {
1362
+ return {
1363
+ eventCount: this.events.length,
1364
+ withMouseData: this.events.filter((e) => e.mouseX !== void 0).length
1365
+ };
1366
+ }
1367
+ };
1368
+
1369
+ // dist/esm/types.js
1370
+ var DEFAULT_SETTINGS = {
1371
+ sampleRates: {
1372
+ mouseMove: 0.1,
1373
+ // Track 10% of mouse moves
1374
+ scroll: 1,
1375
+ // Track ALL scrolls - critical for detection
1376
+ keypress: 1
1377
+ // Track all keypresses
1378
+ },
1379
+ rollingWindows: {
1380
+ mouseMove: 3e4,
1381
+ scroll: 3e4
1382
+ },
1383
+ weights: {
1384
+ mouseMovement: 0.3,
1385
+ // Increased - continuous behavioral signal
1386
+ clickAccuracy: 0.3,
1387
+ // Increased - critical behavioral signal
1388
+ scrollBehavior: 0.15,
1389
+ // Unchanged
1390
+ keyboardTiming: 0.1,
1391
+ // Slightly reduced
1392
+ tabActivity: 0.05,
1393
+ // Unchanged
1394
+ resizeBehavior: 0.02,
1395
+ // Reduced - less reliable
1396
+ environmentFingerprint: 0.08
1397
+ // Reduced - static signal, one-time check
1398
+ },
1399
+ customScorers: {},
1400
+ clickMouseHistoryWindow: 1e3,
1401
+ useWebWorker: false
1402
+ // Will implement Web Worker support in phase 2
1403
+ };
1404
+ // Annotate the CommonJS export names for ESM import in node:
1405
+ 0 && (module.exports = {
1406
+ BehaviorDetector,
1407
+ Click,
1408
+ ClickStrategy,
1409
+ DEFAULT_SETTINGS,
1410
+ Environment,
1411
+ EnvironmentStrategy,
1412
+ Keyboard,
1413
+ KeyboardStrategy,
1414
+ Mouse,
1415
+ MouseStrategy,
1416
+ Resize,
1417
+ ResizeStrategy,
1418
+ Scroll,
1419
+ ScrollStrategy,
1420
+ Tap,
1421
+ TapStrategy
1422
+ });