@axeptio/behavior-detection 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/behavior-detection.esm.min.js +1 -1
- package/dist/behavior-detection.esm.min.js.map +3 -3
- package/dist/behavior-detection.min.js +1 -1
- package/dist/behavior-detection.min.js.map +3 -3
- package/dist/behavior-detector.d.ts +3 -0
- package/dist/cjs/index.cjs +68 -31
- package/dist/esm/behavior-detector.d.ts +3 -0
- package/dist/esm/behavior-detector.js +33 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/strategies/environment.js +56 -35
- package/dist/esm/strategies/mouse.js +1 -1
- package/dist/esm/strategies/scroll.js +1 -1
- package/dist/esm/strategy.d.ts +33 -0
- package/dist/index.d.ts +1 -1
- package/dist/strategy.d.ts +33 -0
- package/package.json +1 -1
|
@@ -15,6 +15,9 @@ export declare class BehaviorDetector {
|
|
|
15
15
|
private confidenceScore;
|
|
16
16
|
private readonly CONFIDENCE_TARGET;
|
|
17
17
|
private readonly CONFIDENCE_DECAY;
|
|
18
|
+
private scoreMemory;
|
|
19
|
+
private scoreFloor;
|
|
20
|
+
private lastEventTime;
|
|
18
21
|
constructor(tickOptions?: TickOptions);
|
|
19
22
|
/**
|
|
20
23
|
* Add a detection strategy
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -46,6 +46,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
46
46
|
// dist/esm/behavior-detector.js
|
|
47
47
|
var BehaviorDetector = class {
|
|
48
48
|
constructor(tickOptions) {
|
|
49
|
+
var _a, _b, _c, _d;
|
|
49
50
|
this.strategies = /* @__PURE__ */ new Map();
|
|
50
51
|
this.isTracking = false;
|
|
51
52
|
this.isPausedByVisibility = false;
|
|
@@ -56,12 +57,21 @@ var BehaviorDetector = class {
|
|
|
56
57
|
this.confidenceScore = 0;
|
|
57
58
|
this.CONFIDENCE_TARGET = 1;
|
|
58
59
|
this.CONFIDENCE_DECAY = 0.95;
|
|
60
|
+
this.scoreFloor = 1;
|
|
61
|
+
this.lastEventTime = 0;
|
|
59
62
|
if (tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.interval) {
|
|
60
63
|
this.tickIntervalMs = tickOptions.interval;
|
|
61
64
|
}
|
|
62
65
|
if ((tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.pauseOnHidden) !== void 0) {
|
|
63
66
|
this.pauseOnHidden = tickOptions.pauseOnHidden;
|
|
64
67
|
}
|
|
68
|
+
const mem = tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.scoreMemory;
|
|
69
|
+
this.scoreMemory = {
|
|
70
|
+
enabled: (_a = mem === null || mem === void 0 ? void 0 : mem.enabled) !== null && _a !== void 0 ? _a : true,
|
|
71
|
+
inactivityThreshold: (_b = mem === null || mem === void 0 ? void 0 : mem.inactivityThreshold) !== null && _b !== void 0 ? _b : 2e3,
|
|
72
|
+
maxRecovery: (_c = mem === null || mem === void 0 ? void 0 : mem.maxRecovery) !== null && _c !== void 0 ? _c : 0.1,
|
|
73
|
+
recoveryPerEvent: (_d = mem === null || mem === void 0 ? void 0 : mem.recoveryPerEvent) !== null && _d !== void 0 ? _d : 0.02
|
|
74
|
+
};
|
|
65
75
|
}
|
|
66
76
|
/**
|
|
67
77
|
* Add a detection strategy
|
|
@@ -90,6 +100,10 @@ var BehaviorDetector = class {
|
|
|
90
100
|
this.confidenceScore *= this.CONFIDENCE_DECAY;
|
|
91
101
|
const contribution = event.weight * strategyWeight;
|
|
92
102
|
this.confidenceScore = Math.min(this.CONFIDENCE_TARGET, this.confidenceScore + contribution);
|
|
103
|
+
this.lastEventTime = Date.now();
|
|
104
|
+
if (this.scoreMemory.enabled) {
|
|
105
|
+
this.scoreFloor = Math.min(1, this.scoreFloor + this.scoreMemory.recoveryPerEvent);
|
|
106
|
+
}
|
|
93
107
|
}
|
|
94
108
|
/**
|
|
95
109
|
* Remove a detection strategy
|
|
@@ -170,6 +184,8 @@ var BehaviorDetector = class {
|
|
|
170
184
|
*/
|
|
171
185
|
reset() {
|
|
172
186
|
this.confidenceScore = 0;
|
|
187
|
+
this.scoreFloor = 1;
|
|
188
|
+
this.lastEventTime = 0;
|
|
173
189
|
for (const [_, config] of this.strategies) {
|
|
174
190
|
config.strategy.reset();
|
|
175
191
|
}
|
|
@@ -179,8 +195,19 @@ var BehaviorDetector = class {
|
|
|
179
195
|
*/
|
|
180
196
|
async score(options = {}) {
|
|
181
197
|
const breakdown = this.calculateStrategyScore();
|
|
198
|
+
let effectiveScore = breakdown.overall;
|
|
199
|
+
if (this.scoreMemory.enabled && this.lastEventTime > 0) {
|
|
200
|
+
if (effectiveScore < this.scoreFloor) {
|
|
201
|
+
this.scoreFloor = effectiveScore;
|
|
202
|
+
}
|
|
203
|
+
const timeSinceEvent = Date.now() - this.lastEventTime;
|
|
204
|
+
if (timeSinceEvent > this.scoreMemory.inactivityThreshold) {
|
|
205
|
+
const cap = this.scoreFloor + this.scoreMemory.maxRecovery;
|
|
206
|
+
effectiveScore = Math.min(effectiveScore, cap);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
182
209
|
const result = {
|
|
183
|
-
score:
|
|
210
|
+
score: effectiveScore
|
|
184
211
|
};
|
|
185
212
|
if (options.breakdown) {
|
|
186
213
|
result.breakdown = breakdown;
|
|
@@ -398,7 +425,7 @@ var MouseStrategy = class extends BaseStrategy {
|
|
|
398
425
|
this.lastPosition = null;
|
|
399
426
|
this.lastAngle = 0;
|
|
400
427
|
this.cumulativeAngle = 0;
|
|
401
|
-
this.rollingWindowMs =
|
|
428
|
+
this.rollingWindowMs = 3e4;
|
|
402
429
|
this.listener = null;
|
|
403
430
|
this.leaveListener = null;
|
|
404
431
|
this.enterListener = null;
|
|
@@ -681,7 +708,7 @@ var ScrollStrategy = class extends BaseStrategy {
|
|
|
681
708
|
this.velocitySeries = [];
|
|
682
709
|
this.lastScrollY = null;
|
|
683
710
|
this.lastTimestamp = 0;
|
|
684
|
-
this.rollingWindowMs =
|
|
711
|
+
this.rollingWindowMs = 3e4;
|
|
685
712
|
this.documentHeight = 1;
|
|
686
713
|
this.listener = null;
|
|
687
714
|
this.isActive = false;
|
|
@@ -1431,6 +1458,33 @@ var AUTOMATION_GLOBALS = [
|
|
|
1431
1458
|
"__fxdriver_unwrapped",
|
|
1432
1459
|
"__selenium_unwrapped"
|
|
1433
1460
|
];
|
|
1461
|
+
function getWebGLRenderer(gl) {
|
|
1462
|
+
const renderer = gl.getParameter(gl.RENDERER);
|
|
1463
|
+
if (renderer && !/^(WebKit WebGL|Mozilla|Google Inc\.)$/i.test(renderer)) {
|
|
1464
|
+
return renderer;
|
|
1465
|
+
}
|
|
1466
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
1467
|
+
if (debugInfo) {
|
|
1468
|
+
return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
1469
|
+
}
|
|
1470
|
+
return renderer !== null && renderer !== void 0 ? renderer : void 0;
|
|
1471
|
+
}
|
|
1472
|
+
function withWebGLContext(fn) {
|
|
1473
|
+
try {
|
|
1474
|
+
const canvas = document.createElement("canvas");
|
|
1475
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
1476
|
+
if (!gl)
|
|
1477
|
+
return { hasWebGL: false, result: void 0 };
|
|
1478
|
+
try {
|
|
1479
|
+
return { hasWebGL: true, result: fn(gl) };
|
|
1480
|
+
} finally {
|
|
1481
|
+
const loseCtx = gl.getExtension("WEBGL_lose_context");
|
|
1482
|
+
loseCtx === null || loseCtx === void 0 ? void 0 : loseCtx.loseContext();
|
|
1483
|
+
}
|
|
1484
|
+
} catch (_a) {
|
|
1485
|
+
return { hasWebGL: false, result: void 0 };
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1434
1488
|
var EnvironmentStrategy = class extends BaseStrategy {
|
|
1435
1489
|
constructor() {
|
|
1436
1490
|
super(...arguments);
|
|
@@ -1447,7 +1501,9 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1447
1501
|
this.data = null;
|
|
1448
1502
|
}
|
|
1449
1503
|
onTick(_timestamp) {
|
|
1450
|
-
this.
|
|
1504
|
+
if (!this.data) {
|
|
1505
|
+
this.captureEnvironment();
|
|
1506
|
+
}
|
|
1451
1507
|
}
|
|
1452
1508
|
score() {
|
|
1453
1509
|
if (!this.data)
|
|
@@ -1545,18 +1601,11 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1545
1601
|
const hasCDPInjection = detectedCDPKeys.length > 0;
|
|
1546
1602
|
const hasNoPlugins = !isMobile && navigator.plugins.length === 0;
|
|
1547
1603
|
let hasSoftwareRenderer = false;
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
if (debugInfo) {
|
|
1554
|
-
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
1555
|
-
hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
|
|
1556
|
-
this._webglRenderer = renderer;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
} catch (_b) {
|
|
1604
|
+
const webglResult = withWebGLContext((gl) => getWebGLRenderer(gl));
|
|
1605
|
+
if (webglResult.hasWebGL && webglResult.result) {
|
|
1606
|
+
const renderer = webglResult.result;
|
|
1607
|
+
hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
|
|
1608
|
+
this._webglRenderer = renderer;
|
|
1560
1609
|
}
|
|
1561
1610
|
return {
|
|
1562
1611
|
isWebdriver,
|
|
@@ -1572,21 +1621,9 @@ var EnvironmentStrategy = class extends BaseStrategy {
|
|
|
1572
1621
|
}
|
|
1573
1622
|
captureEnvironment() {
|
|
1574
1623
|
try {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const canvas = document.createElement("canvas");
|
|
1579
|
-
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
1580
|
-
hasWebGL = !!gl;
|
|
1581
|
-
if (gl) {
|
|
1582
|
-
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
1583
|
-
if (debugInfo) {
|
|
1584
|
-
webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
} catch (e) {
|
|
1588
|
-
hasWebGL = false;
|
|
1589
|
-
}
|
|
1624
|
+
const webgl = withWebGLContext((gl) => getWebGLRenderer(gl));
|
|
1625
|
+
const hasWebGL = webgl.hasWebGL;
|
|
1626
|
+
const webglRenderer = webgl.result;
|
|
1590
1627
|
const hasWebRTC = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
|
|
1591
1628
|
const isMobile = this.isMobileDevice();
|
|
1592
1629
|
const windowWidth = window.innerWidth;
|
|
@@ -15,6 +15,9 @@ export declare class BehaviorDetector {
|
|
|
15
15
|
private confidenceScore;
|
|
16
16
|
private readonly CONFIDENCE_TARGET;
|
|
17
17
|
private readonly CONFIDENCE_DECAY;
|
|
18
|
+
private scoreMemory;
|
|
19
|
+
private scoreFloor;
|
|
20
|
+
private lastEventTime;
|
|
18
21
|
constructor(tickOptions?: TickOptions);
|
|
19
22
|
/**
|
|
20
23
|
* Add a detection strategy
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export class BehaviorDetector {
|
|
6
6
|
constructor(tickOptions) {
|
|
7
|
+
var _a, _b, _c, _d;
|
|
7
8
|
// Strategy mode
|
|
8
9
|
this.strategies = new Map();
|
|
9
10
|
this.isTracking = false;
|
|
@@ -16,12 +17,22 @@ export class BehaviorDetector {
|
|
|
16
17
|
this.confidenceScore = 0;
|
|
17
18
|
this.CONFIDENCE_TARGET = 1.0; // Target confidence for reliable classification
|
|
18
19
|
this.CONFIDENCE_DECAY = 0.95; // Per event decay to prevent infinite growth
|
|
20
|
+
this.scoreFloor = 1.0;
|
|
21
|
+
this.lastEventTime = 0;
|
|
19
22
|
if (tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.interval) {
|
|
20
23
|
this.tickIntervalMs = tickOptions.interval;
|
|
21
24
|
}
|
|
22
25
|
if ((tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.pauseOnHidden) !== undefined) {
|
|
23
26
|
this.pauseOnHidden = tickOptions.pauseOnHidden;
|
|
24
27
|
}
|
|
28
|
+
// Score memory defaults
|
|
29
|
+
const mem = tickOptions === null || tickOptions === void 0 ? void 0 : tickOptions.scoreMemory;
|
|
30
|
+
this.scoreMemory = {
|
|
31
|
+
enabled: (_a = mem === null || mem === void 0 ? void 0 : mem.enabled) !== null && _a !== void 0 ? _a : true,
|
|
32
|
+
inactivityThreshold: (_b = mem === null || mem === void 0 ? void 0 : mem.inactivityThreshold) !== null && _b !== void 0 ? _b : 2000,
|
|
33
|
+
maxRecovery: (_c = mem === null || mem === void 0 ? void 0 : mem.maxRecovery) !== null && _c !== void 0 ? _c : 0.1,
|
|
34
|
+
recoveryPerEvent: (_d = mem === null || mem === void 0 ? void 0 : mem.recoveryPerEvent) !== null && _d !== void 0 ? _d : 0.02,
|
|
35
|
+
};
|
|
25
36
|
}
|
|
26
37
|
/**
|
|
27
38
|
* Add a detection strategy
|
|
@@ -55,6 +66,11 @@ export class BehaviorDetector {
|
|
|
55
66
|
// eventWeight (0-1) * strategyWeight (e.g. 0.3 for mouse) = contribution
|
|
56
67
|
const contribution = event.weight * strategyWeight;
|
|
57
68
|
this.confidenceScore = Math.min(this.CONFIDENCE_TARGET, this.confidenceScore + contribution);
|
|
69
|
+
// Score memory: track activity and slowly raise the floor
|
|
70
|
+
this.lastEventTime = Date.now();
|
|
71
|
+
if (this.scoreMemory.enabled) {
|
|
72
|
+
this.scoreFloor = Math.min(1.0, this.scoreFloor + this.scoreMemory.recoveryPerEvent);
|
|
73
|
+
}
|
|
58
74
|
}
|
|
59
75
|
/**
|
|
60
76
|
* Remove a detection strategy
|
|
@@ -144,6 +160,8 @@ export class BehaviorDetector {
|
|
|
144
160
|
*/
|
|
145
161
|
reset() {
|
|
146
162
|
this.confidenceScore = 0;
|
|
163
|
+
this.scoreFloor = 1.0;
|
|
164
|
+
this.lastEventTime = 0;
|
|
147
165
|
for (const [_, config] of this.strategies) {
|
|
148
166
|
config.strategy.reset();
|
|
149
167
|
}
|
|
@@ -153,8 +171,22 @@ export class BehaviorDetector {
|
|
|
153
171
|
*/
|
|
154
172
|
async score(options = {}) {
|
|
155
173
|
const breakdown = this.calculateStrategyScore();
|
|
174
|
+
let effectiveScore = breakdown.overall;
|
|
175
|
+
// Score memory: cap recovery during inactivity
|
|
176
|
+
if (this.scoreMemory.enabled && this.lastEventTime > 0) {
|
|
177
|
+
// Track the lowest score observed
|
|
178
|
+
if (effectiveScore < this.scoreFloor) {
|
|
179
|
+
this.scoreFloor = effectiveScore;
|
|
180
|
+
}
|
|
181
|
+
// If inactive, prevent the score from recovering freely
|
|
182
|
+
const timeSinceEvent = Date.now() - this.lastEventTime;
|
|
183
|
+
if (timeSinceEvent > this.scoreMemory.inactivityThreshold) {
|
|
184
|
+
const cap = this.scoreFloor + this.scoreMemory.maxRecovery;
|
|
185
|
+
effectiveScore = Math.min(effectiveScore, cap);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
156
188
|
const result = {
|
|
157
|
-
score:
|
|
189
|
+
score: effectiveScore,
|
|
158
190
|
};
|
|
159
191
|
if (options.breakdown) {
|
|
160
192
|
result.breakdown = breakdown;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export { BehaviorDetector } from './behavior-detector.js';
|
|
35
|
-
export type { DetectionStrategy, StrategyConfig } from './strategy.js';
|
|
35
|
+
export type { DetectionStrategy, StrategyConfig, ScoreMemoryOptions } from './strategy.js';
|
|
36
36
|
export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, Timing, Visibility, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, TimingStrategy, VisibilityStrategy, } from './strategies/index.js';
|
|
37
37
|
export type { BehaviorSettings, ScoreOptions, ScoreResult, ScoreBreakdown, TrackedEvent, EventType, ScoringFunction, } from './types.js';
|
|
38
38
|
export { DEFAULT_SETTINGS } from './types.js';
|
|
@@ -30,6 +30,46 @@ const AUTOMATION_GLOBALS = [
|
|
|
30
30
|
'__fxdriver_unwrapped',
|
|
31
31
|
'__selenium_unwrapped',
|
|
32
32
|
];
|
|
33
|
+
/**
|
|
34
|
+
* Get the WebGL renderer string without triggering deprecation warnings.
|
|
35
|
+
* Modern browsers (Firefox 128+, Chrome 113+) return the unmasked renderer
|
|
36
|
+
* directly from gl.RENDERER. Falls back to the deprecated
|
|
37
|
+
* WEBGL_debug_renderer_info extension for older browsers.
|
|
38
|
+
*/
|
|
39
|
+
function getWebGLRenderer(gl) {
|
|
40
|
+
// Modern browsers: gl.RENDERER now returns the unmasked string directly
|
|
41
|
+
const renderer = gl.getParameter(gl.RENDERER);
|
|
42
|
+
if (renderer && !/^(WebKit WebGL|Mozilla|Google Inc\.)$/i.test(renderer)) {
|
|
43
|
+
return renderer;
|
|
44
|
+
}
|
|
45
|
+
// Fallback for older browsers that return a generic RENDERER string
|
|
46
|
+
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
47
|
+
if (debugInfo) {
|
|
48
|
+
return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
49
|
+
}
|
|
50
|
+
return renderer !== null && renderer !== void 0 ? renderer : undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a temporary WebGL context, run a callback, then release it.
|
|
54
|
+
*/
|
|
55
|
+
function withWebGLContext(fn) {
|
|
56
|
+
try {
|
|
57
|
+
const canvas = document.createElement('canvas');
|
|
58
|
+
const gl = (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
|
|
59
|
+
if (!gl)
|
|
60
|
+
return { hasWebGL: false, result: undefined };
|
|
61
|
+
try {
|
|
62
|
+
return { hasWebGL: true, result: fn(gl) };
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
const loseCtx = gl.getExtension('WEBGL_lose_context');
|
|
66
|
+
loseCtx === null || loseCtx === void 0 ? void 0 : loseCtx.loseContext();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (_a) {
|
|
70
|
+
return { hasWebGL: false, result: undefined };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
33
73
|
export class EnvironmentStrategy extends BaseStrategy {
|
|
34
74
|
constructor() {
|
|
35
75
|
super(...arguments);
|
|
@@ -47,8 +87,11 @@ export class EnvironmentStrategy extends BaseStrategy {
|
|
|
47
87
|
this.data = null;
|
|
48
88
|
}
|
|
49
89
|
onTick(_timestamp) {
|
|
50
|
-
//
|
|
51
|
-
|
|
90
|
+
// Only capture once - environment data is static and creating
|
|
91
|
+
// WebGL contexts on every tick leaks GPU resources
|
|
92
|
+
if (!this.data) {
|
|
93
|
+
this.captureEnvironment();
|
|
94
|
+
}
|
|
52
95
|
}
|
|
53
96
|
score() {
|
|
54
97
|
if (!this.data)
|
|
@@ -179,23 +222,14 @@ export class EnvironmentStrategy extends BaseStrategy {
|
|
|
179
222
|
const hasNoPlugins = !isMobile && navigator.plugins.length === 0;
|
|
180
223
|
// 7. Software renderer detection (SwiftShader, ANGLE without GPU)
|
|
181
224
|
let hasSoftwareRenderer = false;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// ANGLE without real GPU = suspicious
|
|
191
|
-
hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
|
|
192
|
-
// Store for debugging
|
|
193
|
-
this._webglRenderer = renderer;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (_b) {
|
|
198
|
-
// WebGL not available
|
|
225
|
+
const webglResult = withWebGLContext((gl) => getWebGLRenderer(gl));
|
|
226
|
+
if (webglResult.hasWebGL && webglResult.result) {
|
|
227
|
+
const renderer = webglResult.result;
|
|
228
|
+
// SwiftShader = headless Chrome software renderer
|
|
229
|
+
// ANGLE without real GPU = suspicious
|
|
230
|
+
hasSoftwareRenderer = /SwiftShader|Software|LLVMpipe/i.test(renderer);
|
|
231
|
+
// Store for debugging
|
|
232
|
+
this._webglRenderer = renderer;
|
|
199
233
|
}
|
|
200
234
|
return {
|
|
201
235
|
isWebdriver,
|
|
@@ -212,22 +246,9 @@ export class EnvironmentStrategy extends BaseStrategy {
|
|
|
212
246
|
captureEnvironment() {
|
|
213
247
|
try {
|
|
214
248
|
// WebGL detection
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const canvas = document.createElement('canvas');
|
|
219
|
-
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
220
|
-
hasWebGL = !!gl;
|
|
221
|
-
if (gl) {
|
|
222
|
-
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
223
|
-
if (debugInfo) {
|
|
224
|
-
webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
catch (e) {
|
|
229
|
-
hasWebGL = false;
|
|
230
|
-
}
|
|
249
|
+
const webgl = withWebGLContext((gl) => getWebGLRenderer(gl));
|
|
250
|
+
const hasWebGL = webgl.hasWebGL;
|
|
251
|
+
const webglRenderer = webgl.result;
|
|
231
252
|
// WebRTC detection
|
|
232
253
|
const hasWebRTC = !!(window.RTCPeerConnection ||
|
|
233
254
|
window.mozRTCPeerConnection ||
|
|
@@ -20,7 +20,7 @@ export class MouseStrategy extends BaseStrategy {
|
|
|
20
20
|
this.lastPosition = null;
|
|
21
21
|
this.lastAngle = 0;
|
|
22
22
|
this.cumulativeAngle = 0;
|
|
23
|
-
this.rollingWindowMs =
|
|
23
|
+
this.rollingWindowMs = 30000;
|
|
24
24
|
this.listener = null;
|
|
25
25
|
this.leaveListener = null;
|
|
26
26
|
this.enterListener = null;
|
|
@@ -14,7 +14,7 @@ export class ScrollStrategy extends BaseStrategy {
|
|
|
14
14
|
this.velocitySeries = [];
|
|
15
15
|
this.lastScrollY = null;
|
|
16
16
|
this.lastTimestamp = 0;
|
|
17
|
-
this.rollingWindowMs =
|
|
17
|
+
this.rollingWindowMs = 30000;
|
|
18
18
|
this.documentHeight = 1;
|
|
19
19
|
this.listener = null;
|
|
20
20
|
this.isActive = false;
|
package/dist/esm/strategy.d.ts
CHANGED
|
@@ -104,4 +104,37 @@ export interface TickOptions {
|
|
|
104
104
|
* Default: true (recommended for better performance and battery life)
|
|
105
105
|
*/
|
|
106
106
|
pauseOnHidden?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Score memory options to prevent score recovery during inactivity.
|
|
109
|
+
* When enabled, the detector remembers the lowest score observed and prevents
|
|
110
|
+
* the score from jumping back up when behavioral data expires from rolling windows.
|
|
111
|
+
* Default: enabled with sensible defaults
|
|
112
|
+
*/
|
|
113
|
+
scoreMemory?: ScoreMemoryOptions;
|
|
114
|
+
}
|
|
115
|
+
export interface ScoreMemoryOptions {
|
|
116
|
+
/**
|
|
117
|
+
* Enable score memory (default: true)
|
|
118
|
+
* When enabled, the detector tracks the lowest score observed and caps
|
|
119
|
+
* score recovery during periods of inactivity.
|
|
120
|
+
*/
|
|
121
|
+
enabled: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Milliseconds of inactivity before the score cap kicks in (default: 2000)
|
|
124
|
+
* If no strategy events are received for this duration, the score cannot
|
|
125
|
+
* recover freely — it's capped near the lowest observed score.
|
|
126
|
+
*/
|
|
127
|
+
inactivityThreshold?: number;
|
|
128
|
+
/**
|
|
129
|
+
* Maximum score increase allowed above the floor during inactivity (default: 0.1)
|
|
130
|
+
* During inactivity, effective score = min(rawScore, floor + maxRecovery)
|
|
131
|
+
*/
|
|
132
|
+
maxRecovery?: number;
|
|
133
|
+
/**
|
|
134
|
+
* How much the floor rises per strategy event (default: 0.02)
|
|
135
|
+
* Each incoming event nudges the floor upward, allowing genuine human
|
|
136
|
+
* activity to eventually recover the score. At 0.02, it takes ~15 events
|
|
137
|
+
* to recover 0.3 points of score.
|
|
138
|
+
*/
|
|
139
|
+
recoveryPerEvent?: number;
|
|
107
140
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export { BehaviorDetector } from './behavior-detector.js';
|
|
35
|
-
export type { DetectionStrategy, StrategyConfig } from './strategy.js';
|
|
35
|
+
export type { DetectionStrategy, StrategyConfig, ScoreMemoryOptions } from './strategy.js';
|
|
36
36
|
export { Mouse, Scroll, Click, Tap, Keyboard, Environment, Resize, Timing, Visibility, MouseStrategy, ScrollStrategy, ClickStrategy, TapStrategy, KeyboardStrategy, EnvironmentStrategy, ResizeStrategy, TimingStrategy, VisibilityStrategy, } from './strategies/index.js';
|
|
37
37
|
export type { BehaviorSettings, ScoreOptions, ScoreResult, ScoreBreakdown, TrackedEvent, EventType, ScoringFunction, } from './types.js';
|
|
38
38
|
export { DEFAULT_SETTINGS } from './types.js';
|
package/dist/strategy.d.ts
CHANGED
|
@@ -104,4 +104,37 @@ export interface TickOptions {
|
|
|
104
104
|
* Default: true (recommended for better performance and battery life)
|
|
105
105
|
*/
|
|
106
106
|
pauseOnHidden?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Score memory options to prevent score recovery during inactivity.
|
|
109
|
+
* When enabled, the detector remembers the lowest score observed and prevents
|
|
110
|
+
* the score from jumping back up when behavioral data expires from rolling windows.
|
|
111
|
+
* Default: enabled with sensible defaults
|
|
112
|
+
*/
|
|
113
|
+
scoreMemory?: ScoreMemoryOptions;
|
|
114
|
+
}
|
|
115
|
+
export interface ScoreMemoryOptions {
|
|
116
|
+
/**
|
|
117
|
+
* Enable score memory (default: true)
|
|
118
|
+
* When enabled, the detector tracks the lowest score observed and caps
|
|
119
|
+
* score recovery during periods of inactivity.
|
|
120
|
+
*/
|
|
121
|
+
enabled: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Milliseconds of inactivity before the score cap kicks in (default: 2000)
|
|
124
|
+
* If no strategy events are received for this duration, the score cannot
|
|
125
|
+
* recover freely — it's capped near the lowest observed score.
|
|
126
|
+
*/
|
|
127
|
+
inactivityThreshold?: number;
|
|
128
|
+
/**
|
|
129
|
+
* Maximum score increase allowed above the floor during inactivity (default: 0.1)
|
|
130
|
+
* During inactivity, effective score = min(rawScore, floor + maxRecovery)
|
|
131
|
+
*/
|
|
132
|
+
maxRecovery?: number;
|
|
133
|
+
/**
|
|
134
|
+
* How much the floor rises per strategy event (default: 0.02)
|
|
135
|
+
* Each incoming event nudges the floor upward, allowing genuine human
|
|
136
|
+
* activity to eventually recover the score. At 0.02, it takes ~15 events
|
|
137
|
+
* to recover 0.3 points of score.
|
|
138
|
+
*/
|
|
139
|
+
recoveryPerEvent?: number;
|
|
107
140
|
}
|
package/package.json
CHANGED