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