@loamly/tracker 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +411 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.mjs +411 -1
- package/dist/index.mjs.map +1 -1
- package/dist/loamly.iife.global.js +411 -1
- package/dist/loamly.iife.global.js.map +1 -1
- package/dist/loamly.iife.min.global.js +1 -1
- package/dist/loamly.iife.min.global.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +1 -1
- package/src/core.ts +135 -1
- package/src/detection/behavioral-classifier.ts +489 -0
- package/src/detection/index.ts +6 -2
- package/src/types.ts +21 -0
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(index_exports);
|
|
33
33
|
|
|
34
34
|
// src/config.ts
|
|
35
|
-
var VERSION = "1.
|
|
35
|
+
var VERSION = "1.7.0";
|
|
36
36
|
var DEFAULT_CONFIG = {
|
|
37
37
|
apiHost: "https://app.loamly.ai",
|
|
38
38
|
endpoints: {
|
|
@@ -209,6 +209,322 @@ function detectAIFromUTM(url) {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// src/detection/behavioral-classifier.ts
|
|
213
|
+
var NAIVE_BAYES_WEIGHTS = {
|
|
214
|
+
human: {
|
|
215
|
+
time_to_first_click_delayed: 0.85,
|
|
216
|
+
time_to_first_click_normal: 0.75,
|
|
217
|
+
time_to_first_click_fast: 0.5,
|
|
218
|
+
time_to_first_click_immediate: 0.25,
|
|
219
|
+
scroll_speed_variable: 0.8,
|
|
220
|
+
scroll_speed_erratic: 0.7,
|
|
221
|
+
scroll_speed_uniform: 0.35,
|
|
222
|
+
scroll_speed_none: 0.45,
|
|
223
|
+
nav_timing_click: 0.75,
|
|
224
|
+
nav_timing_unknown: 0.55,
|
|
225
|
+
nav_timing_paste: 0.35,
|
|
226
|
+
has_referrer: 0.7,
|
|
227
|
+
no_referrer: 0.45,
|
|
228
|
+
homepage_landing: 0.65,
|
|
229
|
+
deep_landing: 0.5,
|
|
230
|
+
mouse_movement_curved: 0.9,
|
|
231
|
+
mouse_movement_linear: 0.3,
|
|
232
|
+
mouse_movement_none: 0.4,
|
|
233
|
+
form_fill_normal: 0.85,
|
|
234
|
+
form_fill_fast: 0.6,
|
|
235
|
+
form_fill_instant: 0.2,
|
|
236
|
+
focus_blur_normal: 0.75,
|
|
237
|
+
focus_blur_rapid: 0.45
|
|
238
|
+
},
|
|
239
|
+
ai_influenced: {
|
|
240
|
+
time_to_first_click_immediate: 0.75,
|
|
241
|
+
time_to_first_click_fast: 0.55,
|
|
242
|
+
time_to_first_click_normal: 0.4,
|
|
243
|
+
time_to_first_click_delayed: 0.35,
|
|
244
|
+
scroll_speed_none: 0.55,
|
|
245
|
+
scroll_speed_uniform: 0.7,
|
|
246
|
+
scroll_speed_variable: 0.35,
|
|
247
|
+
scroll_speed_erratic: 0.4,
|
|
248
|
+
nav_timing_paste: 0.75,
|
|
249
|
+
nav_timing_unknown: 0.5,
|
|
250
|
+
nav_timing_click: 0.35,
|
|
251
|
+
no_referrer: 0.65,
|
|
252
|
+
has_referrer: 0.4,
|
|
253
|
+
deep_landing: 0.6,
|
|
254
|
+
homepage_landing: 0.45,
|
|
255
|
+
mouse_movement_none: 0.6,
|
|
256
|
+
mouse_movement_linear: 0.75,
|
|
257
|
+
mouse_movement_curved: 0.25,
|
|
258
|
+
form_fill_instant: 0.8,
|
|
259
|
+
form_fill_fast: 0.55,
|
|
260
|
+
form_fill_normal: 0.3,
|
|
261
|
+
focus_blur_rapid: 0.6,
|
|
262
|
+
focus_blur_normal: 0.4
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var PRIORS = {
|
|
266
|
+
human: 0.85,
|
|
267
|
+
ai_influenced: 0.15
|
|
268
|
+
};
|
|
269
|
+
var DEFAULT_WEIGHT = 0.5;
|
|
270
|
+
var BehavioralClassifier = class {
|
|
271
|
+
/**
|
|
272
|
+
* Create a new classifier
|
|
273
|
+
* @param minSessionTimeMs Minimum session time before classification (default: 10s)
|
|
274
|
+
*/
|
|
275
|
+
constructor(minSessionTimeMs = 1e4) {
|
|
276
|
+
this.classified = false;
|
|
277
|
+
this.result = null;
|
|
278
|
+
this.onClassify = null;
|
|
279
|
+
this.minSessionTime = minSessionTimeMs;
|
|
280
|
+
this.data = {
|
|
281
|
+
firstClickTime: null,
|
|
282
|
+
scrollEvents: [],
|
|
283
|
+
mouseEvents: [],
|
|
284
|
+
formEvents: [],
|
|
285
|
+
focusBlurEvents: [],
|
|
286
|
+
startTime: Date.now()
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Set callback for when classification completes
|
|
291
|
+
*/
|
|
292
|
+
setOnClassify(callback) {
|
|
293
|
+
this.onClassify = callback;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Record a click event
|
|
297
|
+
*/
|
|
298
|
+
recordClick() {
|
|
299
|
+
if (this.data.firstClickTime === null) {
|
|
300
|
+
this.data.firstClickTime = Date.now();
|
|
301
|
+
}
|
|
302
|
+
this.checkAndClassify();
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Record a scroll event
|
|
306
|
+
*/
|
|
307
|
+
recordScroll(position) {
|
|
308
|
+
this.data.scrollEvents.push({ time: Date.now(), position });
|
|
309
|
+
if (this.data.scrollEvents.length > 50) {
|
|
310
|
+
this.data.scrollEvents = this.data.scrollEvents.slice(-50);
|
|
311
|
+
}
|
|
312
|
+
this.checkAndClassify();
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Record mouse movement
|
|
316
|
+
*/
|
|
317
|
+
recordMouse(x, y) {
|
|
318
|
+
this.data.mouseEvents.push({ time: Date.now(), x, y });
|
|
319
|
+
if (this.data.mouseEvents.length > 100) {
|
|
320
|
+
this.data.mouseEvents = this.data.mouseEvents.slice(-100);
|
|
321
|
+
}
|
|
322
|
+
this.checkAndClassify();
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Record form field interaction start
|
|
326
|
+
*/
|
|
327
|
+
recordFormStart(fieldId) {
|
|
328
|
+
const existing = this.data.formEvents.find((e) => e.fieldId === fieldId && e.endTime === 0);
|
|
329
|
+
if (!existing) {
|
|
330
|
+
this.data.formEvents.push({ fieldId, startTime: Date.now(), endTime: 0 });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Record form field interaction end
|
|
335
|
+
*/
|
|
336
|
+
recordFormEnd(fieldId) {
|
|
337
|
+
const event = this.data.formEvents.find((e) => e.fieldId === fieldId && e.endTime === 0);
|
|
338
|
+
if (event) {
|
|
339
|
+
event.endTime = Date.now();
|
|
340
|
+
}
|
|
341
|
+
this.checkAndClassify();
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Record focus/blur event
|
|
345
|
+
*/
|
|
346
|
+
recordFocusBlur(type) {
|
|
347
|
+
this.data.focusBlurEvents.push({ type, time: Date.now() });
|
|
348
|
+
if (this.data.focusBlurEvents.length > 20) {
|
|
349
|
+
this.data.focusBlurEvents = this.data.focusBlurEvents.slice(-20);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Check if we have enough data and classify
|
|
354
|
+
*/
|
|
355
|
+
checkAndClassify() {
|
|
356
|
+
if (this.classified) return;
|
|
357
|
+
const sessionDuration = Date.now() - this.data.startTime;
|
|
358
|
+
if (sessionDuration < this.minSessionTime) return;
|
|
359
|
+
const hasData = this.data.scrollEvents.length >= 2 || this.data.mouseEvents.length >= 5 || this.data.firstClickTime !== null;
|
|
360
|
+
if (!hasData) return;
|
|
361
|
+
this.classify();
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Force classification (for beforeunload)
|
|
365
|
+
*/
|
|
366
|
+
forceClassify() {
|
|
367
|
+
if (this.classified) return this.result;
|
|
368
|
+
return this.classify();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Perform classification
|
|
372
|
+
*/
|
|
373
|
+
classify() {
|
|
374
|
+
const sessionDuration = Date.now() - this.data.startTime;
|
|
375
|
+
const signals = this.extractSignals();
|
|
376
|
+
let humanLogProb = Math.log(PRIORS.human);
|
|
377
|
+
let aiLogProb = Math.log(PRIORS.ai_influenced);
|
|
378
|
+
for (const signal of signals) {
|
|
379
|
+
const humanWeight = NAIVE_BAYES_WEIGHTS.human[signal] ?? DEFAULT_WEIGHT;
|
|
380
|
+
const aiWeight = NAIVE_BAYES_WEIGHTS.ai_influenced[signal] ?? DEFAULT_WEIGHT;
|
|
381
|
+
humanLogProb += Math.log(humanWeight);
|
|
382
|
+
aiLogProb += Math.log(aiWeight);
|
|
383
|
+
}
|
|
384
|
+
const maxLog = Math.max(humanLogProb, aiLogProb);
|
|
385
|
+
const humanExp = Math.exp(humanLogProb - maxLog);
|
|
386
|
+
const aiExp = Math.exp(aiLogProb - maxLog);
|
|
387
|
+
const total = humanExp + aiExp;
|
|
388
|
+
const humanProbability = humanExp / total;
|
|
389
|
+
const aiProbability = aiExp / total;
|
|
390
|
+
let classification;
|
|
391
|
+
let confidence;
|
|
392
|
+
if (humanProbability > 0.6) {
|
|
393
|
+
classification = "human";
|
|
394
|
+
confidence = humanProbability;
|
|
395
|
+
} else if (aiProbability > 0.6) {
|
|
396
|
+
classification = "ai_influenced";
|
|
397
|
+
confidence = aiProbability;
|
|
398
|
+
} else {
|
|
399
|
+
classification = "uncertain";
|
|
400
|
+
confidence = Math.max(humanProbability, aiProbability);
|
|
401
|
+
}
|
|
402
|
+
this.result = {
|
|
403
|
+
classification,
|
|
404
|
+
humanProbability,
|
|
405
|
+
aiProbability,
|
|
406
|
+
confidence,
|
|
407
|
+
signals,
|
|
408
|
+
timestamp: Date.now(),
|
|
409
|
+
sessionDurationMs: sessionDuration
|
|
410
|
+
};
|
|
411
|
+
this.classified = true;
|
|
412
|
+
if (this.onClassify) {
|
|
413
|
+
this.onClassify(this.result);
|
|
414
|
+
}
|
|
415
|
+
return this.result;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Extract behavioral signals from collected data
|
|
419
|
+
*/
|
|
420
|
+
extractSignals() {
|
|
421
|
+
const signals = [];
|
|
422
|
+
if (this.data.firstClickTime !== null) {
|
|
423
|
+
const timeToClick = this.data.firstClickTime - this.data.startTime;
|
|
424
|
+
if (timeToClick < 500) {
|
|
425
|
+
signals.push("time_to_first_click_immediate");
|
|
426
|
+
} else if (timeToClick < 2e3) {
|
|
427
|
+
signals.push("time_to_first_click_fast");
|
|
428
|
+
} else if (timeToClick < 1e4) {
|
|
429
|
+
signals.push("time_to_first_click_normal");
|
|
430
|
+
} else {
|
|
431
|
+
signals.push("time_to_first_click_delayed");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (this.data.scrollEvents.length === 0) {
|
|
435
|
+
signals.push("scroll_speed_none");
|
|
436
|
+
} else if (this.data.scrollEvents.length >= 3) {
|
|
437
|
+
const scrollDeltas = [];
|
|
438
|
+
for (let i = 1; i < this.data.scrollEvents.length; i++) {
|
|
439
|
+
const delta = this.data.scrollEvents[i].time - this.data.scrollEvents[i - 1].time;
|
|
440
|
+
scrollDeltas.push(delta);
|
|
441
|
+
}
|
|
442
|
+
const mean = scrollDeltas.reduce((a, b) => a + b, 0) / scrollDeltas.length;
|
|
443
|
+
const variance = scrollDeltas.reduce((sum, d) => sum + Math.pow(d - mean, 2), 0) / scrollDeltas.length;
|
|
444
|
+
const stdDev = Math.sqrt(variance);
|
|
445
|
+
const cv = mean > 0 ? stdDev / mean : 0;
|
|
446
|
+
if (cv < 0.2) {
|
|
447
|
+
signals.push("scroll_speed_uniform");
|
|
448
|
+
} else if (cv < 0.6) {
|
|
449
|
+
signals.push("scroll_speed_variable");
|
|
450
|
+
} else {
|
|
451
|
+
signals.push("scroll_speed_erratic");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (this.data.mouseEvents.length === 0) {
|
|
455
|
+
signals.push("mouse_movement_none");
|
|
456
|
+
} else if (this.data.mouseEvents.length >= 10) {
|
|
457
|
+
const n = Math.min(this.data.mouseEvents.length, 20);
|
|
458
|
+
const recentMouse = this.data.mouseEvents.slice(-n);
|
|
459
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
|
460
|
+
for (const event of recentMouse) {
|
|
461
|
+
sumX += event.x;
|
|
462
|
+
sumY += event.y;
|
|
463
|
+
sumXY += event.x * event.y;
|
|
464
|
+
sumX2 += event.x * event.x;
|
|
465
|
+
}
|
|
466
|
+
const denominator = n * sumX2 - sumX * sumX;
|
|
467
|
+
const slope = denominator !== 0 ? (n * sumXY - sumX * sumY) / denominator : 0;
|
|
468
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
469
|
+
let ssRes = 0, ssTot = 0;
|
|
470
|
+
const yMean = sumY / n;
|
|
471
|
+
for (const event of recentMouse) {
|
|
472
|
+
const yPred = slope * event.x + intercept;
|
|
473
|
+
ssRes += Math.pow(event.y - yPred, 2);
|
|
474
|
+
ssTot += Math.pow(event.y - yMean, 2);
|
|
475
|
+
}
|
|
476
|
+
const r2 = ssTot !== 0 ? 1 - ssRes / ssTot : 0;
|
|
477
|
+
if (r2 > 0.95) {
|
|
478
|
+
signals.push("mouse_movement_linear");
|
|
479
|
+
} else {
|
|
480
|
+
signals.push("mouse_movement_curved");
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const completedForms = this.data.formEvents.filter((e) => e.endTime > 0);
|
|
484
|
+
if (completedForms.length > 0) {
|
|
485
|
+
const avgFillTime = completedForms.reduce((sum, e) => sum + (e.endTime - e.startTime), 0) / completedForms.length;
|
|
486
|
+
if (avgFillTime < 100) {
|
|
487
|
+
signals.push("form_fill_instant");
|
|
488
|
+
} else if (avgFillTime < 500) {
|
|
489
|
+
signals.push("form_fill_fast");
|
|
490
|
+
} else {
|
|
491
|
+
signals.push("form_fill_normal");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (this.data.focusBlurEvents.length >= 4) {
|
|
495
|
+
const recentFB = this.data.focusBlurEvents.slice(-10);
|
|
496
|
+
const intervals = [];
|
|
497
|
+
for (let i = 1; i < recentFB.length; i++) {
|
|
498
|
+
intervals.push(recentFB[i].time - recentFB[i - 1].time);
|
|
499
|
+
}
|
|
500
|
+
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
|
|
501
|
+
if (avgInterval < 1e3) {
|
|
502
|
+
signals.push("focus_blur_rapid");
|
|
503
|
+
} else {
|
|
504
|
+
signals.push("focus_blur_normal");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return signals;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Add context signals (set by tracker from external data)
|
|
511
|
+
*/
|
|
512
|
+
addContextSignal(_signal) {
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Get current result (null if not yet classified)
|
|
516
|
+
*/
|
|
517
|
+
getResult() {
|
|
518
|
+
return this.result;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Check if classification has been performed
|
|
522
|
+
*/
|
|
523
|
+
hasClassified() {
|
|
524
|
+
return this.classified;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
212
528
|
// src/utils.ts
|
|
213
529
|
function generateUUID() {
|
|
214
530
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
@@ -294,6 +610,8 @@ var sessionId = null;
|
|
|
294
610
|
var sessionStartTime = null;
|
|
295
611
|
var navigationTiming = null;
|
|
296
612
|
var aiDetection = null;
|
|
613
|
+
var behavioralClassifier = null;
|
|
614
|
+
var behavioralMLResult = null;
|
|
297
615
|
function log(...args) {
|
|
298
616
|
if (debugMode) {
|
|
299
617
|
console.log("[Loamly]", ...args);
|
|
@@ -333,6 +651,9 @@ function init(userConfig = {}) {
|
|
|
333
651
|
if (!userConfig.disableBehavioral) {
|
|
334
652
|
setupBehavioralTracking();
|
|
335
653
|
}
|
|
654
|
+
behavioralClassifier = new BehavioralClassifier(1e4);
|
|
655
|
+
behavioralClassifier.setOnClassify(handleBehavioralClassification);
|
|
656
|
+
setupBehavioralMLTracking();
|
|
336
657
|
log("Initialization complete");
|
|
337
658
|
}
|
|
338
659
|
function pageview(customUrl) {
|
|
@@ -514,6 +835,89 @@ function sendBehavioralEvent(eventType, data) {
|
|
|
514
835
|
body: JSON.stringify(payload)
|
|
515
836
|
});
|
|
516
837
|
}
|
|
838
|
+
function setupBehavioralMLTracking() {
|
|
839
|
+
if (!behavioralClassifier) return;
|
|
840
|
+
let mouseSampleCount = 0;
|
|
841
|
+
document.addEventListener("mousemove", (e) => {
|
|
842
|
+
mouseSampleCount++;
|
|
843
|
+
if (mouseSampleCount % 10 === 0 && behavioralClassifier) {
|
|
844
|
+
behavioralClassifier.recordMouse(e.clientX, e.clientY);
|
|
845
|
+
}
|
|
846
|
+
}, { passive: true });
|
|
847
|
+
document.addEventListener("click", () => {
|
|
848
|
+
if (behavioralClassifier) {
|
|
849
|
+
behavioralClassifier.recordClick();
|
|
850
|
+
}
|
|
851
|
+
}, { passive: true });
|
|
852
|
+
let lastScrollY = 0;
|
|
853
|
+
document.addEventListener("scroll", () => {
|
|
854
|
+
const currentY = window.scrollY;
|
|
855
|
+
if (Math.abs(currentY - lastScrollY) > 50 && behavioralClassifier) {
|
|
856
|
+
lastScrollY = currentY;
|
|
857
|
+
behavioralClassifier.recordScroll(currentY);
|
|
858
|
+
}
|
|
859
|
+
}, { passive: true });
|
|
860
|
+
document.addEventListener("focusin", (e) => {
|
|
861
|
+
if (behavioralClassifier) {
|
|
862
|
+
behavioralClassifier.recordFocusBlur("focus");
|
|
863
|
+
const target = e.target;
|
|
864
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
|
|
865
|
+
behavioralClassifier.recordFormStart(target.id || target.getAttribute("name") || "unknown");
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}, { passive: true });
|
|
869
|
+
document.addEventListener("focusout", (e) => {
|
|
870
|
+
if (behavioralClassifier) {
|
|
871
|
+
behavioralClassifier.recordFocusBlur("blur");
|
|
872
|
+
const target = e.target;
|
|
873
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
|
|
874
|
+
behavioralClassifier.recordFormEnd(target.id || target.getAttribute("name") || "unknown");
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}, { passive: true });
|
|
878
|
+
window.addEventListener("beforeunload", () => {
|
|
879
|
+
if (behavioralClassifier && !behavioralClassifier.hasClassified()) {
|
|
880
|
+
const result = behavioralClassifier.forceClassify();
|
|
881
|
+
if (result) {
|
|
882
|
+
handleBehavioralClassification(result);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
setTimeout(() => {
|
|
887
|
+
if (behavioralClassifier && !behavioralClassifier.hasClassified()) {
|
|
888
|
+
behavioralClassifier.forceClassify();
|
|
889
|
+
}
|
|
890
|
+
}, 3e4);
|
|
891
|
+
}
|
|
892
|
+
function handleBehavioralClassification(result) {
|
|
893
|
+
log("Behavioral ML classification:", result);
|
|
894
|
+
behavioralMLResult = {
|
|
895
|
+
classification: result.classification,
|
|
896
|
+
humanProbability: result.humanProbability,
|
|
897
|
+
aiProbability: result.aiProbability,
|
|
898
|
+
confidence: result.confidence,
|
|
899
|
+
signals: result.signals,
|
|
900
|
+
sessionDurationMs: result.sessionDurationMs
|
|
901
|
+
};
|
|
902
|
+
sendBehavioralEvent("ml_classification", {
|
|
903
|
+
classification: result.classification,
|
|
904
|
+
human_probability: result.humanProbability,
|
|
905
|
+
ai_probability: result.aiProbability,
|
|
906
|
+
confidence: result.confidence,
|
|
907
|
+
signals: result.signals,
|
|
908
|
+
session_duration_ms: result.sessionDurationMs,
|
|
909
|
+
navigation_timing: navigationTiming,
|
|
910
|
+
ai_detection: aiDetection
|
|
911
|
+
});
|
|
912
|
+
if (result.classification === "ai_influenced" && result.confidence >= 0.7) {
|
|
913
|
+
aiDetection = {
|
|
914
|
+
isAI: true,
|
|
915
|
+
confidence: result.confidence,
|
|
916
|
+
method: "behavioral"
|
|
917
|
+
};
|
|
918
|
+
log("AI detection updated from behavioral ML:", aiDetection);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
517
921
|
function getCurrentSessionId() {
|
|
518
922
|
return sessionId;
|
|
519
923
|
}
|
|
@@ -526,6 +930,9 @@ function getAIDetectionResult() {
|
|
|
526
930
|
function getNavigationTimingResult() {
|
|
527
931
|
return navigationTiming;
|
|
528
932
|
}
|
|
933
|
+
function getBehavioralMLResult() {
|
|
934
|
+
return behavioralMLResult;
|
|
935
|
+
}
|
|
529
936
|
function isTrackerInitialized() {
|
|
530
937
|
return initialized;
|
|
531
938
|
}
|
|
@@ -537,6 +944,8 @@ function reset() {
|
|
|
537
944
|
sessionStartTime = null;
|
|
538
945
|
navigationTiming = null;
|
|
539
946
|
aiDetection = null;
|
|
947
|
+
behavioralClassifier = null;
|
|
948
|
+
behavioralMLResult = null;
|
|
540
949
|
try {
|
|
541
950
|
sessionStorage.removeItem("loamly_session");
|
|
542
951
|
sessionStorage.removeItem("loamly_start");
|
|
@@ -557,6 +966,7 @@ var loamly = {
|
|
|
557
966
|
getVisitorId: getCurrentVisitorId,
|
|
558
967
|
getAIDetection: getAIDetectionResult,
|
|
559
968
|
getNavigationTiming: getNavigationTimingResult,
|
|
969
|
+
getBehavioralML: getBehavioralMLResult,
|
|
560
970
|
isInitialized: isTrackerInitialized,
|
|
561
971
|
reset,
|
|
562
972
|
debug: setDebug
|