@axeptio/behavior-detection 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -12
- package/dist/behavior-detection.esm.min.js +1 -1
- package/dist/behavior-detection.esm.min.js.map +4 -4
- package/dist/behavior-detection.min.js +1 -1
- package/dist/behavior-detection.min.js.map +3 -3
- package/dist/cjs/index.cjs +937 -29
- package/dist/esm/browser.js +0 -2
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/strategies/click.d.ts +8 -0
- package/dist/esm/strategies/click.js +108 -9
- package/dist/esm/strategies/environment.d.ts +39 -0
- package/dist/esm/strategies/environment.js +218 -16
- package/dist/esm/strategies/index.d.ts +4 -0
- package/dist/esm/strategies/index.js +4 -0
- package/dist/esm/strategies/mouse.d.ts +53 -1
- package/dist/esm/strategies/mouse.js +198 -2
- package/dist/esm/strategies/timing.d.ts +64 -0
- package/dist/esm/strategies/timing.js +308 -0
- package/dist/esm/strategies/visibility.d.ts +64 -0
- package/dist/esm/strategies/visibility.js +295 -0
- package/dist/index.d.ts +1 -1
- package/dist/strategies/click.d.ts +8 -0
- package/dist/strategies/environment.d.ts +39 -0
- package/dist/strategies/index.d.ts +4 -0
- package/dist/strategies/mouse.d.ts +53 -1
- package/dist/strategies/timing.d.ts +64 -0
- package/dist/strategies/visibility.d.ts +64 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -35,7 +35,11 @@ __export(index_exports, {
|
|
|
35
35
|
Scroll: () => ScrollStrategy,
|
|
36
36
|
ScrollStrategy: () => ScrollStrategy,
|
|
37
37
|
Tap: () => TapStrategy,
|
|
38
|
-
TapStrategy: () => TapStrategy
|
|
38
|
+
TapStrategy: () => TapStrategy,
|
|
39
|
+
Timing: () => TimingStrategy,
|
|
40
|
+
TimingStrategy: () => TimingStrategy,
|
|
41
|
+
Visibility: () => VisibilityStrategy,
|
|
42
|
+
VisibilityStrategy: () => VisibilityStrategy
|
|
39
43
|
});
|
|
40
44
|
module.exports = __toCommonJS(index_exports);
|
|
41
45
|
|
|
@@ -397,8 +401,12 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
397
401
|
this.rollingWindowMs = 5e3;
|
|
398
402
|
this.listener = null;
|
|
399
403
|
this.leaveListener = null;
|
|
404
|
+
this.enterListener = null;
|
|
400
405
|
this.isActive = false;
|
|
401
406
|
this.screenDiagonal = 1;
|
|
407
|
+
this.entryPoints = [];
|
|
408
|
+
this.microMovements = [];
|
|
409
|
+
this.STILLNESS_WINDOW = 500;
|
|
402
410
|
if ((options === null || options === void 0 ? void 0 : options.rollingWindow) !== void 0)
|
|
403
411
|
this.rollingWindowMs = options.rollingWindow;
|
|
404
412
|
}
|
|
@@ -412,11 +420,18 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
412
420
|
this.listener = (e) => {
|
|
413
421
|
const mouseEvent = e;
|
|
414
422
|
const now = Date.now();
|
|
415
|
-
const currentPos = { x: mouseEvent.clientX, y: mouseEvent.clientY };
|
|
423
|
+
const currentPos = { x: mouseEvent.clientX, y: mouseEvent.clientY, timestamp: now };
|
|
416
424
|
if (this.lastPosition) {
|
|
417
425
|
const dx = currentPos.x - this.lastPosition.x;
|
|
418
426
|
const dy = currentPos.y - this.lastPosition.y;
|
|
419
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
|
+
}
|
|
420
435
|
const normalizedDistance = pixelDistance / this.screenDiagonal;
|
|
421
436
|
if (normalizedDistance > 1e-3) {
|
|
422
437
|
const rawAngle = Math.atan2(dy, dx);
|
|
@@ -440,12 +455,62 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
440
455
|
this.lastPosition = currentPos;
|
|
441
456
|
};
|
|
442
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 });
|
|
443
507
|
this.leaveListener = () => {
|
|
444
508
|
this.distanceSeries = [];
|
|
445
509
|
this.angleSeries = [];
|
|
446
510
|
this.lastPosition = null;
|
|
447
511
|
this.lastAngle = 0;
|
|
448
512
|
this.cumulativeAngle = 0;
|
|
513
|
+
this.microMovements = [];
|
|
449
514
|
};
|
|
450
515
|
document.addEventListener("mouseleave", this.leaveListener, { passive: true });
|
|
451
516
|
}
|
|
@@ -461,6 +526,10 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
461
526
|
document.removeEventListener("mouseleave", this.leaveListener);
|
|
462
527
|
this.leaveListener = null;
|
|
463
528
|
}
|
|
529
|
+
if (this.enterListener) {
|
|
530
|
+
document.removeEventListener("mouseenter", this.enterListener);
|
|
531
|
+
this.enterListener = null;
|
|
532
|
+
}
|
|
464
533
|
}
|
|
465
534
|
reset() {
|
|
466
535
|
this.distanceSeries = [];
|
|
@@ -468,6 +537,8 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
468
537
|
this.lastPosition = null;
|
|
469
538
|
this.lastAngle = 0;
|
|
470
539
|
this.cumulativeAngle = 0;
|
|
540
|
+
this.entryPoints = [];
|
|
541
|
+
this.microMovements = [];
|
|
471
542
|
}
|
|
472
543
|
score() {
|
|
473
544
|
if (this.distanceSeries.length < 10)
|
|
@@ -476,7 +547,7 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
476
547
|
}
|
|
477
548
|
/**
|
|
478
549
|
* Mouse-specific pattern detection
|
|
479
|
-
* Detects bot-like patterns: constant velocity, linear paths
|
|
550
|
+
* Detects bot-like patterns: constant velocity, linear paths, suspicious entry points
|
|
480
551
|
*/
|
|
481
552
|
detectMousePatterns() {
|
|
482
553
|
if (this.distanceSeries.length < 10)
|
|
@@ -499,15 +570,103 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
499
570
|
score += gaussian(avgChange, 0.15, 0.12);
|
|
500
571
|
factors++;
|
|
501
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
|
+
}
|
|
502
583
|
return factors > 0 ? score / factors : void 0;
|
|
503
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
|
+
}
|
|
504
660
|
getDebugInfo() {
|
|
505
661
|
return {
|
|
506
662
|
eventCount: this.distanceSeries.length,
|
|
507
663
|
rollingWindow: this.rollingWindowMs,
|
|
508
664
|
isActive: this.isActive,
|
|
509
665
|
distanceSeries: this.distanceSeries,
|
|
510
|
-
angleSeries: this.angleSeries
|
|
666
|
+
angleSeries: this.angleSeries,
|
|
667
|
+
entryPoints: this.entryPoints,
|
|
668
|
+
microMovementCount: this.microMovements.length,
|
|
669
|
+
lastPosition: this.lastPosition
|
|
511
670
|
};
|
|
512
671
|
}
|
|
513
672
|
};
|
|
@@ -654,7 +813,8 @@ var ClickStrategy = class extends BaseStrategy {
|
|
|
654
813
|
const mouseEvent = e;
|
|
655
814
|
this.lastMousePosition = {
|
|
656
815
|
x: mouseEvent.clientX,
|
|
657
|
-
y: mouseEvent.clientY
|
|
816
|
+
y: mouseEvent.clientY,
|
|
817
|
+
timestamp: Date.now()
|
|
658
818
|
};
|
|
659
819
|
};
|
|
660
820
|
document.addEventListener("mousemove", this.mouseListener, { passive: true });
|
|
@@ -684,8 +844,17 @@ var ClickStrategy = class extends BaseStrategy {
|
|
|
684
844
|
const listener = (e) => {
|
|
685
845
|
var _a, _b;
|
|
686
846
|
const clickEvent = e;
|
|
847
|
+
const now = Date.now();
|
|
687
848
|
const rect = element.getBoundingClientRect();
|
|
688
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
|
+
}
|
|
689
858
|
let position;
|
|
690
859
|
if (!this.lastMousePosition) {
|
|
691
860
|
position = "no-mouse-data";
|
|
@@ -714,7 +883,10 @@ var ClickStrategy = class extends BaseStrategy {
|
|
|
714
883
|
rect,
|
|
715
884
|
inViewport,
|
|
716
885
|
position,
|
|
717
|
-
timestamp:
|
|
886
|
+
timestamp: now,
|
|
887
|
+
mouseClickDelta,
|
|
888
|
+
timeSinceMouseMove,
|
|
889
|
+
isTrusted: clickEvent.isTrusted
|
|
718
890
|
});
|
|
719
891
|
this.notifyEvent(1);
|
|
720
892
|
};
|
|
@@ -740,26 +912,93 @@ var ClickStrategy = class extends BaseStrategy {
|
|
|
740
912
|
this.lastMousePosition = null;
|
|
741
913
|
}
|
|
742
914
|
score() {
|
|
743
|
-
|
|
915
|
+
const eventCount = this.events.length;
|
|
916
|
+
if (eventCount === 0)
|
|
744
917
|
return void 0;
|
|
745
|
-
let
|
|
918
|
+
let positionScore = 0;
|
|
919
|
+
let clicksWithDelta = 0;
|
|
920
|
+
let mismatchCount = 0;
|
|
921
|
+
let clicksWithTiming = 0;
|
|
922
|
+
let suspiciousTimingCount = 0;
|
|
923
|
+
let untrustedCount = 0;
|
|
746
924
|
for (const click of this.events) {
|
|
747
925
|
switch (click.position) {
|
|
748
926
|
case "no-mouse-data":
|
|
749
|
-
|
|
927
|
+
positionScore += 0;
|
|
750
928
|
break;
|
|
751
929
|
case "outside":
|
|
752
|
-
|
|
930
|
+
positionScore += 0;
|
|
753
931
|
break;
|
|
754
932
|
case "dead-center":
|
|
755
|
-
|
|
933
|
+
positionScore += 0.5;
|
|
756
934
|
break;
|
|
757
935
|
case "over-element":
|
|
758
|
-
|
|
936
|
+
positionScore += 1;
|
|
759
937
|
break;
|
|
760
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++;
|
|
761
972
|
}
|
|
762
|
-
|
|
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;
|
|
763
1002
|
}
|
|
764
1003
|
getDebugInfo() {
|
|
765
1004
|
return {
|
|
@@ -771,7 +1010,10 @@ var ClickStrategy = class extends BaseStrategy {
|
|
|
771
1010
|
overElement: this.events.filter((e) => e.position === "over-element").length
|
|
772
1011
|
},
|
|
773
1012
|
inViewport: this.events.filter((e) => e.inViewport).length,
|
|
774
|
-
trackedElements: this.clickListeners.size
|
|
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
|
|
775
1017
|
};
|
|
776
1018
|
}
|
|
777
1019
|
};
|
|
@@ -1168,6 +1410,54 @@ var KeyboardStrategy = class extends BaseStrategy {
|
|
|
1168
1410
|
};
|
|
1169
1411
|
|
|
1170
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
|
+
function getWebGLRenderer(gl) {
|
|
1435
|
+
const renderer = gl.getParameter(gl.RENDERER);
|
|
1436
|
+
if (renderer && !/^(WebKit WebGL|Mozilla|Google Inc\.)$/i.test(renderer)) {
|
|
1437
|
+
return renderer;
|
|
1438
|
+
}
|
|
1439
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
1440
|
+
if (debugInfo) {
|
|
1441
|
+
return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
1442
|
+
}
|
|
1443
|
+
return renderer !== null && renderer !== void 0 ? renderer : void 0;
|
|
1444
|
+
}
|
|
1445
|
+
function withWebGLContext(fn) {
|
|
1446
|
+
try {
|
|
1447
|
+
const canvas = document.createElement("canvas");
|
|
1448
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
1449
|
+
if (!gl)
|
|
1450
|
+
return { hasWebGL: false, result: void 0 };
|
|
1451
|
+
try {
|
|
1452
|
+
return { hasWebGL: true, result: fn(gl) };
|
|
1453
|
+
} finally {
|
|
1454
|
+
const loseCtx = gl.getExtension("WEBGL_lose_context");
|
|
1455
|
+
loseCtx === null || loseCtx === void 0 ? void 0 : loseCtx.loseContext();
|
|
1456
|
+
}
|
|
1457
|
+
} catch (_a) {
|
|
1458
|
+
return { hasWebGL: false, result: void 0 };
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1171
1461
|
var EnvironmentStrategy = class extends BaseStrategy {
|
|
1172
1462
|
constructor() {
|
|
1173
1463
|
super(...arguments);
|
|
@@ -1184,12 +1474,35 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1184
1474
|
this.data = null;
|
|
1185
1475
|
}
|
|
1186
1476
|
onTick(_timestamp) {
|
|
1187
|
-
this.
|
|
1477
|
+
if (!this.data) {
|
|
1478
|
+
this.captureEnvironment();
|
|
1479
|
+
}
|
|
1188
1480
|
}
|
|
1189
1481
|
score() {
|
|
1190
1482
|
if (!this.data)
|
|
1191
1483
|
return void 0;
|
|
1192
1484
|
const env = this.data;
|
|
1485
|
+
const auto = env.automation;
|
|
1486
|
+
if (auto.isWebdriver) {
|
|
1487
|
+
return 0.05;
|
|
1488
|
+
}
|
|
1489
|
+
if (auto.hasAutomationGlobals) {
|
|
1490
|
+
return 0.1;
|
|
1491
|
+
}
|
|
1492
|
+
if (auto.hasCDPInjection) {
|
|
1493
|
+
return 0.1;
|
|
1494
|
+
}
|
|
1495
|
+
const headlessIndicatorCount = [
|
|
1496
|
+
auto.hasHeadlessUA,
|
|
1497
|
+
auto.hasChromelessRuntime,
|
|
1498
|
+
auto.hasSoftwareRenderer
|
|
1499
|
+
].filter(Boolean).length;
|
|
1500
|
+
if (headlessIndicatorCount >= 2) {
|
|
1501
|
+
return 0.15;
|
|
1502
|
+
}
|
|
1503
|
+
if (auto.hasHeadlessUA) {
|
|
1504
|
+
return 0.2;
|
|
1505
|
+
}
|
|
1193
1506
|
let score = 0;
|
|
1194
1507
|
let factors = 0;
|
|
1195
1508
|
score += env.suspiciousDimensions ? 0.1 : 1;
|
|
@@ -1204,9 +1517,23 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1204
1517
|
].filter(Boolean).length;
|
|
1205
1518
|
score += featureCount / 4;
|
|
1206
1519
|
factors++;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1520
|
+
if (!env.isMobile) {
|
|
1521
|
+
score += inverseSigmoid(env.plugins, -2, -0.5);
|
|
1522
|
+
score += env.plugins > 0 ? 1 : 0.1;
|
|
1523
|
+
factors += 2;
|
|
1524
|
+
if (auto.hasNoPlugins && env.vendor.includes("Google")) {
|
|
1525
|
+
score += 0.3;
|
|
1526
|
+
factors++;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if (auto.hasChromelessRuntime) {
|
|
1530
|
+
score += 0.3;
|
|
1531
|
+
factors++;
|
|
1532
|
+
}
|
|
1533
|
+
if (auto.hasSoftwareRenderer) {
|
|
1534
|
+
score += 0.4;
|
|
1535
|
+
factors++;
|
|
1536
|
+
}
|
|
1210
1537
|
score += gaussian(env.devicePixelRatio, 2, 1.5);
|
|
1211
1538
|
score += env.colorDepth === 24 || env.colorDepth === 32 ? 1 : 0.4;
|
|
1212
1539
|
factors += 2;
|
|
@@ -1218,15 +1545,58 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1218
1545
|
const smallScreen = window.innerWidth < 768 && window.innerHeight < 1024;
|
|
1219
1546
|
return hasTouchScreen && smallScreen || mobileUA;
|
|
1220
1547
|
}
|
|
1221
|
-
|
|
1548
|
+
/**
|
|
1549
|
+
* Detect automation indicators
|
|
1550
|
+
*/
|
|
1551
|
+
detectAutomation() {
|
|
1552
|
+
const isMobile = this.isMobileDevice();
|
|
1553
|
+
const isWebdriver = navigator.webdriver === true;
|
|
1554
|
+
const ua = navigator.userAgent;
|
|
1555
|
+
const hasHeadlessUA = /HeadlessChrome|Headless/i.test(ua);
|
|
1556
|
+
const win = window;
|
|
1557
|
+
const hasChromelessRuntime = !!(win.chrome && typeof win.chrome === "object" && !win.chrome.runtime);
|
|
1558
|
+
const detectedGlobals = [];
|
|
1559
|
+
for (const global of AUTOMATION_GLOBALS) {
|
|
1560
|
+
if (global in win) {
|
|
1561
|
+
detectedGlobals.push(global);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
const hasAutomationGlobals = detectedGlobals.length > 0;
|
|
1565
|
+
const detectedCDPKeys = [];
|
|
1222
1566
|
try {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
} catch (e) {
|
|
1228
|
-
hasWebGL = false;
|
|
1567
|
+
for (const key of Object.keys(win)) {
|
|
1568
|
+
if (/^cdc_|^__cdc_/.test(key)) {
|
|
1569
|
+
detectedCDPKeys.push(key);
|
|
1570
|
+
}
|
|
1229
1571
|
}
|
|
1572
|
+
} catch (_a) {
|
|
1573
|
+
}
|
|
1574
|
+
const hasCDPInjection = detectedCDPKeys.length > 0;
|
|
1575
|
+
const hasNoPlugins = !isMobile && navigator.plugins.length === 0;
|
|
1576
|
+
let hasSoftwareRenderer = false;
|
|
1577
|
+
const webglResult = withWebGLContext((gl) => getWebGLRenderer(gl));
|
|
1578
|
+
if (webglResult.hasWebGL && webglResult.result) {
|
|
1579
|
+
const renderer = webglResult.result;
|
|
1580
|
+
hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
|
|
1581
|
+
this._webglRenderer = renderer;
|
|
1582
|
+
}
|
|
1583
|
+
return {
|
|
1584
|
+
isWebdriver,
|
|
1585
|
+
hasHeadlessUA,
|
|
1586
|
+
hasChromelessRuntime,
|
|
1587
|
+
hasSoftwareRenderer,
|
|
1588
|
+
hasNoPlugins,
|
|
1589
|
+
hasAutomationGlobals,
|
|
1590
|
+
detectedGlobals,
|
|
1591
|
+
hasCDPInjection,
|
|
1592
|
+
detectedCDPKeys
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
captureEnvironment() {
|
|
1596
|
+
try {
|
|
1597
|
+
const webgl = withWebGLContext((gl) => getWebGLRenderer(gl));
|
|
1598
|
+
const hasWebGL = webgl.hasWebGL;
|
|
1599
|
+
const webglRenderer = webgl.result;
|
|
1230
1600
|
const hasWebRTC = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
|
|
1231
1601
|
const isMobile = this.isMobileDevice();
|
|
1232
1602
|
const windowWidth = window.innerWidth;
|
|
@@ -1238,6 +1608,7 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1238
1608
|
const suspiciousRatio = windowScreenRatio === 1 || windowScreenRatio < 0.1 || windowScreenRatio > 1;
|
|
1239
1609
|
const hasStorage = typeof localStorage !== "undefined" && typeof sessionStorage !== "undefined";
|
|
1240
1610
|
const featureInconsistency = navigator.plugins.length === 0 && navigator.mimeTypes.length === 0 || !hasWebGL || !hasStorage;
|
|
1611
|
+
const automation = this.detectAutomation();
|
|
1241
1612
|
this.data = {
|
|
1242
1613
|
screenWidth,
|
|
1243
1614
|
screenHeight,
|
|
@@ -1263,10 +1634,11 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1263
1634
|
suspiciousDimensions,
|
|
1264
1635
|
featureInconsistency,
|
|
1265
1636
|
isMobile,
|
|
1266
|
-
timestamp: Date.now()
|
|
1637
|
+
timestamp: Date.now(),
|
|
1638
|
+
automation,
|
|
1639
|
+
webglRenderer
|
|
1267
1640
|
};
|
|
1268
|
-
} catch (
|
|
1269
|
-
console.warn("Failed to capture environment:", error);
|
|
1641
|
+
} catch (_a) {
|
|
1270
1642
|
}
|
|
1271
1643
|
}
|
|
1272
1644
|
getDebugInfo() {
|
|
@@ -1281,6 +1653,24 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1281
1653
|
var _a, _b;
|
|
1282
1654
|
return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.isMobile) !== null && _b !== void 0 ? _b : null;
|
|
1283
1655
|
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Get automation detection results
|
|
1658
|
+
* Returns null if environment hasn't been captured yet
|
|
1659
|
+
*/
|
|
1660
|
+
getAutomationIndicators() {
|
|
1661
|
+
var _a, _b;
|
|
1662
|
+
return (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.automation) !== null && _b !== void 0 ? _b : null;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Quick check if any strong automation indicator is present
|
|
1666
|
+
* Returns true if likely a bot, false if likely human, null if not checked yet
|
|
1667
|
+
*/
|
|
1668
|
+
isLikelyBot() {
|
|
1669
|
+
if (!this.data)
|
|
1670
|
+
return null;
|
|
1671
|
+
const auto = this.data.automation;
|
|
1672
|
+
return auto.isWebdriver || auto.hasAutomationGlobals || auto.hasCDPInjection || auto.hasHeadlessUA;
|
|
1673
|
+
}
|
|
1284
1674
|
};
|
|
1285
1675
|
|
|
1286
1676
|
// dist/esm/strategies/resize.js
|
|
@@ -1366,6 +1756,520 @@ var ResizeStrategy = class extends BaseStrategy {
|
|
|
1366
1756
|
}
|
|
1367
1757
|
};
|
|
1368
1758
|
|
|
1759
|
+
// dist/esm/strategies/timing.js
|
|
1760
|
+
var TimingStrategy = class extends BaseStrategy {
|
|
1761
|
+
constructor() {
|
|
1762
|
+
super(...arguments);
|
|
1763
|
+
this.name = "timing";
|
|
1764
|
+
this.defaultWeight = 0.15;
|
|
1765
|
+
this.actions = [];
|
|
1766
|
+
this.mouseStillness = {
|
|
1767
|
+
position: null,
|
|
1768
|
+
lastMoveTime: 0,
|
|
1769
|
+
microMovementCount: 0
|
|
1770
|
+
};
|
|
1771
|
+
this.microMovements = [];
|
|
1772
|
+
this.isActive = false;
|
|
1773
|
+
this.mouseListener = null;
|
|
1774
|
+
this.clickListener = null;
|
|
1775
|
+
this.keydownListener = null;
|
|
1776
|
+
this.scrollListener = null;
|
|
1777
|
+
this.STILLNESS_WINDOW = 500;
|
|
1778
|
+
this.MICRO_MOVEMENT_THRESHOLD = 1;
|
|
1779
|
+
this.MAX_MICRO_MOVEMENT = 5;
|
|
1780
|
+
this.MACHINE_PRECISION_THRESHOLD = 5;
|
|
1781
|
+
}
|
|
1782
|
+
start() {
|
|
1783
|
+
if (this.isActive)
|
|
1784
|
+
return;
|
|
1785
|
+
this.isActive = true;
|
|
1786
|
+
this.mouseListener = (e) => {
|
|
1787
|
+
const mouseEvent = e;
|
|
1788
|
+
const now = Date.now();
|
|
1789
|
+
const currentPos = { x: mouseEvent.clientX, y: mouseEvent.clientY };
|
|
1790
|
+
if (this.mouseStillness.position) {
|
|
1791
|
+
const dx = currentPos.x - this.mouseStillness.position.x;
|
|
1792
|
+
const dy = currentPos.y - this.mouseStillness.position.y;
|
|
1793
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
1794
|
+
if (distance >= this.MICRO_MOVEMENT_THRESHOLD && distance <= this.MAX_MICRO_MOVEMENT) {
|
|
1795
|
+
this.microMovements.push({ timestamp: now, distance });
|
|
1796
|
+
}
|
|
1797
|
+
if (distance >= this.MICRO_MOVEMENT_THRESHOLD) {
|
|
1798
|
+
this.mouseStillness.lastMoveTime = now;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
this.mouseStillness.position = currentPos;
|
|
1802
|
+
const cutoff = now - this.STILLNESS_WINDOW;
|
|
1803
|
+
this.microMovements = this.microMovements.filter((m) => m.timestamp >= cutoff);
|
|
1804
|
+
};
|
|
1805
|
+
this.clickListener = (e) => {
|
|
1806
|
+
this.recordAction("click", e);
|
|
1807
|
+
};
|
|
1808
|
+
this.keydownListener = () => {
|
|
1809
|
+
this.recordAction("keydown");
|
|
1810
|
+
};
|
|
1811
|
+
this.scrollListener = () => {
|
|
1812
|
+
this.recordAction("scroll");
|
|
1813
|
+
};
|
|
1814
|
+
document.addEventListener("mousemove", this.mouseListener, { passive: true });
|
|
1815
|
+
document.addEventListener("click", this.clickListener, { passive: true });
|
|
1816
|
+
document.addEventListener("keydown", this.keydownListener, { passive: true });
|
|
1817
|
+
document.addEventListener("scroll", this.scrollListener, { passive: true });
|
|
1818
|
+
}
|
|
1819
|
+
recordAction(type, _event) {
|
|
1820
|
+
const now = Date.now();
|
|
1821
|
+
const timeSinceMouseMove = this.mouseStillness.lastMoveTime > 0 ? now - this.mouseStillness.lastMoveTime : -1;
|
|
1822
|
+
this.mouseStillness.microMovementCount = this.microMovements.length;
|
|
1823
|
+
this.actions.push({
|
|
1824
|
+
type,
|
|
1825
|
+
timestamp: now,
|
|
1826
|
+
timeSinceMouseMove,
|
|
1827
|
+
wasHidden: document.hidden
|
|
1828
|
+
});
|
|
1829
|
+
this.notifyEvent(0.7);
|
|
1830
|
+
if (this.actions.length > 100) {
|
|
1831
|
+
this.actions.shift();
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
stop() {
|
|
1835
|
+
if (!this.isActive)
|
|
1836
|
+
return;
|
|
1837
|
+
this.isActive = false;
|
|
1838
|
+
if (this.mouseListener) {
|
|
1839
|
+
document.removeEventListener("mousemove", this.mouseListener);
|
|
1840
|
+
this.mouseListener = null;
|
|
1841
|
+
}
|
|
1842
|
+
if (this.clickListener) {
|
|
1843
|
+
document.removeEventListener("click", this.clickListener);
|
|
1844
|
+
this.clickListener = null;
|
|
1845
|
+
}
|
|
1846
|
+
if (this.keydownListener) {
|
|
1847
|
+
document.removeEventListener("keydown", this.keydownListener);
|
|
1848
|
+
this.keydownListener = null;
|
|
1849
|
+
}
|
|
1850
|
+
if (this.scrollListener) {
|
|
1851
|
+
document.removeEventListener("scroll", this.scrollListener);
|
|
1852
|
+
this.scrollListener = null;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
reset() {
|
|
1856
|
+
this.actions = [];
|
|
1857
|
+
this.microMovements = [];
|
|
1858
|
+
this.mouseStillness = {
|
|
1859
|
+
position: null,
|
|
1860
|
+
lastMoveTime: 0,
|
|
1861
|
+
microMovementCount: 0
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
score() {
|
|
1865
|
+
const actionCount = this.actions.length;
|
|
1866
|
+
if (actionCount < 3)
|
|
1867
|
+
return void 0;
|
|
1868
|
+
let score = 0;
|
|
1869
|
+
let factors = 0;
|
|
1870
|
+
const clicks = [];
|
|
1871
|
+
const clicksWithMouse = [];
|
|
1872
|
+
let hiddenCount = 0;
|
|
1873
|
+
for (const action of this.actions) {
|
|
1874
|
+
if (action.type === "click") {
|
|
1875
|
+
clicks.push(action);
|
|
1876
|
+
if (action.timeSinceMouseMove >= 0) {
|
|
1877
|
+
clicksWithMouse.push(action);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (action.wasHidden)
|
|
1881
|
+
hiddenCount++;
|
|
1882
|
+
}
|
|
1883
|
+
if (clicks.length >= 2) {
|
|
1884
|
+
const stillnessScore = this.scorePreActionStillness(clicks);
|
|
1885
|
+
if (stillnessScore !== void 0) {
|
|
1886
|
+
score += stillnessScore;
|
|
1887
|
+
factors++;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (clicksWithMouse.length >= 3) {
|
|
1891
|
+
const mouseToClickScore = this.scoreMouseToClickDelayFromCache(clicksWithMouse);
|
|
1892
|
+
if (mouseToClickScore !== void 0) {
|
|
1893
|
+
score += mouseToClickScore;
|
|
1894
|
+
factors++;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
if (actionCount >= 5) {
|
|
1898
|
+
const intervalScore = this.scoreActionIntervals();
|
|
1899
|
+
if (intervalScore !== void 0) {
|
|
1900
|
+
score += intervalScore;
|
|
1901
|
+
factors++;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
const hiddenScore = this.scoreHiddenActionsFromCount(hiddenCount, actionCount);
|
|
1905
|
+
score += hiddenScore;
|
|
1906
|
+
factors++;
|
|
1907
|
+
return factors > 0 ? score / factors : void 0;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Score based on pre-action stillness
|
|
1911
|
+
* Humans have micro-movements before clicking; bots are perfectly still
|
|
1912
|
+
*/
|
|
1913
|
+
scorePreActionStillness(clicks) {
|
|
1914
|
+
const clicksWithMouseData = clicks.filter((c) => c.timeSinceMouseMove >= 0);
|
|
1915
|
+
if (clicksWithMouseData.length < 2)
|
|
1916
|
+
return void 0;
|
|
1917
|
+
const perfectlyStillClicks = clicksWithMouseData.filter((c) => {
|
|
1918
|
+
return c.timeSinceMouseMove >= 100 && c.timeSinceMouseMove <= 500 && this.mouseStillness.microMovementCount === 0;
|
|
1919
|
+
}).length;
|
|
1920
|
+
const stillRatio = perfectlyStillClicks / clicksWithMouseData.length;
|
|
1921
|
+
if (stillRatio >= 0.9)
|
|
1922
|
+
return 0.1;
|
|
1923
|
+
if (stillRatio >= 0.7)
|
|
1924
|
+
return 0.3;
|
|
1925
|
+
if (stillRatio >= 0.5)
|
|
1926
|
+
return 0.5;
|
|
1927
|
+
if (stillRatio >= 0.3)
|
|
1928
|
+
return 0.7;
|
|
1929
|
+
return 1;
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Score mouse-to-click delay patterns (using pre-filtered cache)
|
|
1933
|
+
*/
|
|
1934
|
+
scoreMouseToClickDelayFromCache(clicksWithMouse) {
|
|
1935
|
+
const delays = clicksWithMouse.map((c) => c.timeSinceMouseMove);
|
|
1936
|
+
const stats = calculateStatistics(delays);
|
|
1937
|
+
if (stats.mean < 10)
|
|
1938
|
+
return 0.1;
|
|
1939
|
+
if (stats.cv < 0.2 && clicksWithMouse.length >= 5)
|
|
1940
|
+
return 0.2;
|
|
1941
|
+
if (stats.mean >= 50 && stats.mean <= 300 && stats.cv >= 0.3)
|
|
1942
|
+
return 1;
|
|
1943
|
+
if (stats.cv < 0.4)
|
|
1944
|
+
return 0.6;
|
|
1945
|
+
return 0.8;
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Score action intervals for machine-like precision
|
|
1949
|
+
* Detects exact intervals: 100ms, 500ms, 1000ms
|
|
1950
|
+
*/
|
|
1951
|
+
scoreActionIntervals() {
|
|
1952
|
+
if (this.actions.length < 5)
|
|
1953
|
+
return void 0;
|
|
1954
|
+
const intervals = [];
|
|
1955
|
+
for (let i = 1; i < this.actions.length; i++) {
|
|
1956
|
+
intervals.push(this.actions[i].timestamp - this.actions[i - 1].timestamp);
|
|
1957
|
+
}
|
|
1958
|
+
const machineIntervals = [100, 200, 250, 500, 1e3];
|
|
1959
|
+
let preciseCount = 0;
|
|
1960
|
+
for (const interval of intervals) {
|
|
1961
|
+
for (const machineInterval of machineIntervals) {
|
|
1962
|
+
const remainder = interval % machineInterval;
|
|
1963
|
+
if (remainder < this.MACHINE_PRECISION_THRESHOLD || machineInterval - remainder < this.MACHINE_PRECISION_THRESHOLD) {
|
|
1964
|
+
preciseCount++;
|
|
1965
|
+
break;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
const preciseRatio = preciseCount / intervals.length;
|
|
1970
|
+
if (preciseRatio >= 0.8)
|
|
1971
|
+
return 0.1;
|
|
1972
|
+
if (preciseRatio >= 0.6)
|
|
1973
|
+
return 0.3;
|
|
1974
|
+
if (preciseRatio >= 0.4)
|
|
1975
|
+
return 0.5;
|
|
1976
|
+
const stats = calculateStatistics(intervals);
|
|
1977
|
+
if (stats.cv < 0.15)
|
|
1978
|
+
return 0.2;
|
|
1979
|
+
return 1;
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Score based on actions while document was hidden (using pre-computed count)
|
|
1983
|
+
*/
|
|
1984
|
+
scoreHiddenActionsFromCount(hiddenCount, totalCount) {
|
|
1985
|
+
if (hiddenCount === 0)
|
|
1986
|
+
return 1;
|
|
1987
|
+
const hiddenRatio = hiddenCount / totalCount;
|
|
1988
|
+
if (hiddenRatio > 0.5)
|
|
1989
|
+
return 0.1;
|
|
1990
|
+
if (hiddenRatio > 0.2)
|
|
1991
|
+
return 0.3;
|
|
1992
|
+
if (hiddenRatio > 0)
|
|
1993
|
+
return 0.5;
|
|
1994
|
+
return 1;
|
|
1995
|
+
}
|
|
1996
|
+
getDebugInfo() {
|
|
1997
|
+
const clicks = this.actions.filter((a) => a.type === "click");
|
|
1998
|
+
const intervals = [];
|
|
1999
|
+
for (let i = 1; i < this.actions.length; i++) {
|
|
2000
|
+
intervals.push(this.actions[i].timestamp - this.actions[i - 1].timestamp);
|
|
2001
|
+
}
|
|
2002
|
+
return {
|
|
2003
|
+
actionCount: this.actions.length,
|
|
2004
|
+
clickCount: clicks.length,
|
|
2005
|
+
keydownCount: this.actions.filter((a) => a.type === "keydown").length,
|
|
2006
|
+
scrollCount: this.actions.filter((a) => a.type === "scroll").length,
|
|
2007
|
+
hiddenActionCount: this.actions.filter((a) => a.wasHidden).length,
|
|
2008
|
+
microMovementCount: this.microMovements.length,
|
|
2009
|
+
intervals: intervals.slice(-20),
|
|
2010
|
+
// Last 20 intervals
|
|
2011
|
+
lastStillnessDuration: this.mouseStillness.lastMoveTime > 0 ? Date.now() - this.mouseStillness.lastMoveTime : null
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
|
|
2016
|
+
// dist/esm/strategies/visibility.js
|
|
2017
|
+
var VisibilityStrategy = class extends BaseStrategy {
|
|
2018
|
+
constructor() {
|
|
2019
|
+
super(...arguments);
|
|
2020
|
+
this.name = "visibility";
|
|
2021
|
+
this.defaultWeight = 0.1;
|
|
2022
|
+
this.events = [];
|
|
2023
|
+
this.focusTypingPairs = [];
|
|
2024
|
+
this.actionsWhileHidden = 0;
|
|
2025
|
+
this.lastVisibilityChange = null;
|
|
2026
|
+
this.resumeDelays = [];
|
|
2027
|
+
this.isActive = false;
|
|
2028
|
+
this.visibilityListener = null;
|
|
2029
|
+
this.focusListener = null;
|
|
2030
|
+
this.blurListener = null;
|
|
2031
|
+
this.clickListener = null;
|
|
2032
|
+
this.keydownListener = null;
|
|
2033
|
+
this.inputFocusListener = null;
|
|
2034
|
+
this.lastFocusedInput = null;
|
|
2035
|
+
this.hasTypedInFocusedInput = false;
|
|
2036
|
+
this.lastActionTime = 0;
|
|
2037
|
+
this.preHideActionTime = 0;
|
|
2038
|
+
}
|
|
2039
|
+
start() {
|
|
2040
|
+
if (this.isActive)
|
|
2041
|
+
return;
|
|
2042
|
+
this.isActive = true;
|
|
2043
|
+
this.visibilityListener = () => {
|
|
2044
|
+
const now = Date.now();
|
|
2045
|
+
const wasHidden = document.hidden;
|
|
2046
|
+
if (this.lastVisibilityChange && !wasHidden) {
|
|
2047
|
+
if (this.lastActionTime > 0 && this.preHideActionTime > 0) {
|
|
2048
|
+
const timeSinceLastAction = now - this.lastActionTime;
|
|
2049
|
+
if (timeSinceLastAction < 50) {
|
|
2050
|
+
this.actionsWhileHidden++;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
this.lastVisibilityChange = { hidden: false, timestamp: now };
|
|
2054
|
+
} else if (wasHidden) {
|
|
2055
|
+
this.preHideActionTime = this.lastActionTime;
|
|
2056
|
+
this.lastVisibilityChange = { hidden: true, timestamp: now };
|
|
2057
|
+
}
|
|
2058
|
+
this.events.push({
|
|
2059
|
+
type: "visibility_change",
|
|
2060
|
+
timestamp: now,
|
|
2061
|
+
wasHidden
|
|
2062
|
+
});
|
|
2063
|
+
};
|
|
2064
|
+
this.focusListener = () => {
|
|
2065
|
+
this.events.push({
|
|
2066
|
+
type: "focus_change",
|
|
2067
|
+
timestamp: Date.now(),
|
|
2068
|
+
wasHidden: document.hidden,
|
|
2069
|
+
wasFocused: true
|
|
2070
|
+
});
|
|
2071
|
+
};
|
|
2072
|
+
this.blurListener = () => {
|
|
2073
|
+
this.events.push({
|
|
2074
|
+
type: "focus_change",
|
|
2075
|
+
timestamp: Date.now(),
|
|
2076
|
+
wasHidden: document.hidden,
|
|
2077
|
+
wasFocused: false
|
|
2078
|
+
});
|
|
2079
|
+
};
|
|
2080
|
+
this.clickListener = (e) => {
|
|
2081
|
+
this.recordAction("click", e);
|
|
2082
|
+
};
|
|
2083
|
+
this.keydownListener = (e) => {
|
|
2084
|
+
this.recordAction("keydown", e);
|
|
2085
|
+
if (this.lastFocusedInput && !this.hasTypedInFocusedInput) {
|
|
2086
|
+
const now = Date.now();
|
|
2087
|
+
const delay = now - this.lastFocusedInput.timestamp;
|
|
2088
|
+
this.focusTypingPairs.push({
|
|
2089
|
+
focusTime: this.lastFocusedInput.timestamp,
|
|
2090
|
+
firstKeypressTime: now,
|
|
2091
|
+
delay,
|
|
2092
|
+
element: this.lastFocusedInput.element
|
|
2093
|
+
});
|
|
2094
|
+
this.hasTypedInFocusedInput = true;
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
this.inputFocusListener = (e) => {
|
|
2098
|
+
const target = e.target;
|
|
2099
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA")) {
|
|
2100
|
+
this.lastFocusedInput = {
|
|
2101
|
+
element: target.tagName.toLowerCase(),
|
|
2102
|
+
timestamp: Date.now()
|
|
2103
|
+
};
|
|
2104
|
+
this.hasTypedInFocusedInput = false;
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
document.addEventListener("visibilitychange", this.visibilityListener);
|
|
2108
|
+
window.addEventListener("focus", this.focusListener);
|
|
2109
|
+
window.addEventListener("blur", this.blurListener);
|
|
2110
|
+
document.addEventListener("click", this.clickListener, { passive: true });
|
|
2111
|
+
document.addEventListener("keydown", this.keydownListener, { passive: true });
|
|
2112
|
+
document.addEventListener("focusin", this.inputFocusListener, { passive: true });
|
|
2113
|
+
}
|
|
2114
|
+
recordAction(_type, _event) {
|
|
2115
|
+
const now = Date.now();
|
|
2116
|
+
this.lastActionTime = now;
|
|
2117
|
+
if (document.hidden) {
|
|
2118
|
+
this.actionsWhileHidden++;
|
|
2119
|
+
this.events.push({
|
|
2120
|
+
type: "action_while_hidden",
|
|
2121
|
+
timestamp: now,
|
|
2122
|
+
wasHidden: true
|
|
2123
|
+
});
|
|
2124
|
+
this.notifyEvent(1);
|
|
2125
|
+
}
|
|
2126
|
+
if (this.lastVisibilityChange && !this.lastVisibilityChange.hidden) {
|
|
2127
|
+
const resumeDelay = now - this.lastVisibilityChange.timestamp;
|
|
2128
|
+
if (resumeDelay > 0 && resumeDelay < 1e4 && this.resumeDelays.length < 20) {
|
|
2129
|
+
const lastRecordedDelay = this.resumeDelays[this.resumeDelays.length - 1];
|
|
2130
|
+
if (lastRecordedDelay === void 0 || Math.abs(resumeDelay - lastRecordedDelay) > 100) {
|
|
2131
|
+
this.resumeDelays.push(resumeDelay);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
stop() {
|
|
2137
|
+
if (!this.isActive)
|
|
2138
|
+
return;
|
|
2139
|
+
this.isActive = false;
|
|
2140
|
+
if (this.visibilityListener) {
|
|
2141
|
+
document.removeEventListener("visibilitychange", this.visibilityListener);
|
|
2142
|
+
this.visibilityListener = null;
|
|
2143
|
+
}
|
|
2144
|
+
if (this.focusListener) {
|
|
2145
|
+
window.removeEventListener("focus", this.focusListener);
|
|
2146
|
+
this.focusListener = null;
|
|
2147
|
+
}
|
|
2148
|
+
if (this.blurListener) {
|
|
2149
|
+
window.removeEventListener("blur", this.blurListener);
|
|
2150
|
+
this.blurListener = null;
|
|
2151
|
+
}
|
|
2152
|
+
if (this.clickListener) {
|
|
2153
|
+
document.removeEventListener("click", this.clickListener);
|
|
2154
|
+
this.clickListener = null;
|
|
2155
|
+
}
|
|
2156
|
+
if (this.keydownListener) {
|
|
2157
|
+
document.removeEventListener("keydown", this.keydownListener);
|
|
2158
|
+
this.keydownListener = null;
|
|
2159
|
+
}
|
|
2160
|
+
if (this.inputFocusListener) {
|
|
2161
|
+
document.removeEventListener("focusin", this.inputFocusListener);
|
|
2162
|
+
this.inputFocusListener = null;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
reset() {
|
|
2166
|
+
this.events = [];
|
|
2167
|
+
this.focusTypingPairs = [];
|
|
2168
|
+
this.actionsWhileHidden = 0;
|
|
2169
|
+
this.lastVisibilityChange = null;
|
|
2170
|
+
this.resumeDelays = [];
|
|
2171
|
+
this.lastFocusedInput = null;
|
|
2172
|
+
this.hasTypedInFocusedInput = false;
|
|
2173
|
+
this.lastActionTime = 0;
|
|
2174
|
+
this.preHideActionTime = 0;
|
|
2175
|
+
}
|
|
2176
|
+
score() {
|
|
2177
|
+
if (this.events.length < 2)
|
|
2178
|
+
return void 0;
|
|
2179
|
+
let score = 0;
|
|
2180
|
+
let factors = 0;
|
|
2181
|
+
const hiddenActionScore = this.scoreHiddenActions();
|
|
2182
|
+
if (hiddenActionScore !== void 0) {
|
|
2183
|
+
score += hiddenActionScore;
|
|
2184
|
+
factors++;
|
|
2185
|
+
}
|
|
2186
|
+
const resumeScore = this.scoreResumeDelays();
|
|
2187
|
+
if (resumeScore !== void 0) {
|
|
2188
|
+
score += resumeScore;
|
|
2189
|
+
factors++;
|
|
2190
|
+
}
|
|
2191
|
+
const focusTypingScore = this.scoreFocusTyping();
|
|
2192
|
+
if (focusTypingScore !== void 0) {
|
|
2193
|
+
score += focusTypingScore;
|
|
2194
|
+
factors++;
|
|
2195
|
+
}
|
|
2196
|
+
return factors > 0 ? score / factors : void 0;
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Score based on actions while document was hidden
|
|
2200
|
+
* Humans can't interact with hidden tabs; bots can
|
|
2201
|
+
*/
|
|
2202
|
+
scoreHiddenActions() {
|
|
2203
|
+
if (this.actionsWhileHidden === 0)
|
|
2204
|
+
return 1;
|
|
2205
|
+
if (this.actionsWhileHidden >= 5)
|
|
2206
|
+
return 0.1;
|
|
2207
|
+
if (this.actionsWhileHidden >= 3)
|
|
2208
|
+
return 0.2;
|
|
2209
|
+
if (this.actionsWhileHidden >= 1)
|
|
2210
|
+
return 0.3;
|
|
2211
|
+
return 1;
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Score based on how quickly activity resumes after tab becomes visible
|
|
2215
|
+
* Humans need time to refocus (100-500ms minimum)
|
|
2216
|
+
* Bots often resume instantly (< 50ms)
|
|
2217
|
+
*/
|
|
2218
|
+
scoreResumeDelays() {
|
|
2219
|
+
if (this.resumeDelays.length < 2)
|
|
2220
|
+
return void 0;
|
|
2221
|
+
const fastResumeCount = this.resumeDelays.filter((d) => d < 50).length;
|
|
2222
|
+
const fastResumeRatio = fastResumeCount / this.resumeDelays.length;
|
|
2223
|
+
if (fastResumeRatio >= 0.8)
|
|
2224
|
+
return 0.1;
|
|
2225
|
+
if (fastResumeRatio >= 0.5)
|
|
2226
|
+
return 0.3;
|
|
2227
|
+
if (fastResumeRatio >= 0.3)
|
|
2228
|
+
return 0.5;
|
|
2229
|
+
if (fastResumeRatio > 0)
|
|
2230
|
+
return 0.7;
|
|
2231
|
+
return 1;
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Score based on focus-to-keypress timing
|
|
2235
|
+
* Humans focus inputs before typing (natural delay 100-500ms)
|
|
2236
|
+
* Bots often type without focusing or with instant delay
|
|
2237
|
+
*/
|
|
2238
|
+
scoreFocusTyping() {
|
|
2239
|
+
if (this.focusTypingPairs.length < 2)
|
|
2240
|
+
return void 0;
|
|
2241
|
+
let suspiciousCount = 0;
|
|
2242
|
+
for (const pair of this.focusTypingPairs) {
|
|
2243
|
+
if (pair.delay < 20) {
|
|
2244
|
+
suspiciousCount++;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const suspiciousRatio = suspiciousCount / this.focusTypingPairs.length;
|
|
2248
|
+
if (suspiciousRatio >= 0.8)
|
|
2249
|
+
return 0.2;
|
|
2250
|
+
if (suspiciousRatio >= 0.5)
|
|
2251
|
+
return 0.4;
|
|
2252
|
+
if (suspiciousRatio >= 0.3)
|
|
2253
|
+
return 0.6;
|
|
2254
|
+
if (suspiciousRatio > 0)
|
|
2255
|
+
return 0.8;
|
|
2256
|
+
return 1;
|
|
2257
|
+
}
|
|
2258
|
+
getDebugInfo() {
|
|
2259
|
+
return {
|
|
2260
|
+
eventCount: this.events.length,
|
|
2261
|
+
actionsWhileHidden: this.actionsWhileHidden,
|
|
2262
|
+
visibilityChanges: this.events.filter((e) => e.type === "visibility_change").length,
|
|
2263
|
+
focusChanges: this.events.filter((e) => e.type === "focus_change").length,
|
|
2264
|
+
resumeDelays: this.resumeDelays,
|
|
2265
|
+
focusTypingPairs: this.focusTypingPairs.map((p) => ({
|
|
2266
|
+
delay: p.delay,
|
|
2267
|
+
element: p.element
|
|
2268
|
+
}))
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
|
|
1369
2273
|
// dist/esm/types.js
|
|
1370
2274
|
var DEFAULT_SETTINGS = {
|
|
1371
2275
|
sampleRates: {
|
|
@@ -1418,5 +2322,9 @@ var DEFAULT_SETTINGS = {
|
|
|
1418
2322
|
Scroll,
|
|
1419
2323
|
ScrollStrategy,
|
|
1420
2324
|
Tap,
|
|
1421
|
-
TapStrategy
|
|
2325
|
+
TapStrategy,
|
|
2326
|
+
Timing,
|
|
2327
|
+
TimingStrategy,
|
|
2328
|
+
Visibility,
|
|
2329
|
+
VisibilityStrategy
|
|
1422
2330
|
});
|