@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.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
var VERSION = "1.
|
|
2
|
+
var VERSION = "1.7.0";
|
|
3
3
|
var DEFAULT_CONFIG = {
|
|
4
4
|
apiHost: "https://app.loamly.ai",
|
|
5
5
|
endpoints: {
|
|
@@ -176,6 +176,322 @@ function detectAIFromUTM(url) {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// src/detection/behavioral-classifier.ts
|
|
180
|
+
var NAIVE_BAYES_WEIGHTS = {
|
|
181
|
+
human: {
|
|
182
|
+
time_to_first_click_delayed: 0.85,
|
|
183
|
+
time_to_first_click_normal: 0.75,
|
|
184
|
+
time_to_first_click_fast: 0.5,
|
|
185
|
+
time_to_first_click_immediate: 0.25,
|
|
186
|
+
scroll_speed_variable: 0.8,
|
|
187
|
+
scroll_speed_erratic: 0.7,
|
|
188
|
+
scroll_speed_uniform: 0.35,
|
|
189
|
+
scroll_speed_none: 0.45,
|
|
190
|
+
nav_timing_click: 0.75,
|
|
191
|
+
nav_timing_unknown: 0.55,
|
|
192
|
+
nav_timing_paste: 0.35,
|
|
193
|
+
has_referrer: 0.7,
|
|
194
|
+
no_referrer: 0.45,
|
|
195
|
+
homepage_landing: 0.65,
|
|
196
|
+
deep_landing: 0.5,
|
|
197
|
+
mouse_movement_curved: 0.9,
|
|
198
|
+
mouse_movement_linear: 0.3,
|
|
199
|
+
mouse_movement_none: 0.4,
|
|
200
|
+
form_fill_normal: 0.85,
|
|
201
|
+
form_fill_fast: 0.6,
|
|
202
|
+
form_fill_instant: 0.2,
|
|
203
|
+
focus_blur_normal: 0.75,
|
|
204
|
+
focus_blur_rapid: 0.45
|
|
205
|
+
},
|
|
206
|
+
ai_influenced: {
|
|
207
|
+
time_to_first_click_immediate: 0.75,
|
|
208
|
+
time_to_first_click_fast: 0.55,
|
|
209
|
+
time_to_first_click_normal: 0.4,
|
|
210
|
+
time_to_first_click_delayed: 0.35,
|
|
211
|
+
scroll_speed_none: 0.55,
|
|
212
|
+
scroll_speed_uniform: 0.7,
|
|
213
|
+
scroll_speed_variable: 0.35,
|
|
214
|
+
scroll_speed_erratic: 0.4,
|
|
215
|
+
nav_timing_paste: 0.75,
|
|
216
|
+
nav_timing_unknown: 0.5,
|
|
217
|
+
nav_timing_click: 0.35,
|
|
218
|
+
no_referrer: 0.65,
|
|
219
|
+
has_referrer: 0.4,
|
|
220
|
+
deep_landing: 0.6,
|
|
221
|
+
homepage_landing: 0.45,
|
|
222
|
+
mouse_movement_none: 0.6,
|
|
223
|
+
mouse_movement_linear: 0.75,
|
|
224
|
+
mouse_movement_curved: 0.25,
|
|
225
|
+
form_fill_instant: 0.8,
|
|
226
|
+
form_fill_fast: 0.55,
|
|
227
|
+
form_fill_normal: 0.3,
|
|
228
|
+
focus_blur_rapid: 0.6,
|
|
229
|
+
focus_blur_normal: 0.4
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var PRIORS = {
|
|
233
|
+
human: 0.85,
|
|
234
|
+
ai_influenced: 0.15
|
|
235
|
+
};
|
|
236
|
+
var DEFAULT_WEIGHT = 0.5;
|
|
237
|
+
var BehavioralClassifier = class {
|
|
238
|
+
/**
|
|
239
|
+
* Create a new classifier
|
|
240
|
+
* @param minSessionTimeMs Minimum session time before classification (default: 10s)
|
|
241
|
+
*/
|
|
242
|
+
constructor(minSessionTimeMs = 1e4) {
|
|
243
|
+
this.classified = false;
|
|
244
|
+
this.result = null;
|
|
245
|
+
this.onClassify = null;
|
|
246
|
+
this.minSessionTime = minSessionTimeMs;
|
|
247
|
+
this.data = {
|
|
248
|
+
firstClickTime: null,
|
|
249
|
+
scrollEvents: [],
|
|
250
|
+
mouseEvents: [],
|
|
251
|
+
formEvents: [],
|
|
252
|
+
focusBlurEvents: [],
|
|
253
|
+
startTime: Date.now()
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Set callback for when classification completes
|
|
258
|
+
*/
|
|
259
|
+
setOnClassify(callback) {
|
|
260
|
+
this.onClassify = callback;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Record a click event
|
|
264
|
+
*/
|
|
265
|
+
recordClick() {
|
|
266
|
+
if (this.data.firstClickTime === null) {
|
|
267
|
+
this.data.firstClickTime = Date.now();
|
|
268
|
+
}
|
|
269
|
+
this.checkAndClassify();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Record a scroll event
|
|
273
|
+
*/
|
|
274
|
+
recordScroll(position) {
|
|
275
|
+
this.data.scrollEvents.push({ time: Date.now(), position });
|
|
276
|
+
if (this.data.scrollEvents.length > 50) {
|
|
277
|
+
this.data.scrollEvents = this.data.scrollEvents.slice(-50);
|
|
278
|
+
}
|
|
279
|
+
this.checkAndClassify();
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Record mouse movement
|
|
283
|
+
*/
|
|
284
|
+
recordMouse(x, y) {
|
|
285
|
+
this.data.mouseEvents.push({ time: Date.now(), x, y });
|
|
286
|
+
if (this.data.mouseEvents.length > 100) {
|
|
287
|
+
this.data.mouseEvents = this.data.mouseEvents.slice(-100);
|
|
288
|
+
}
|
|
289
|
+
this.checkAndClassify();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Record form field interaction start
|
|
293
|
+
*/
|
|
294
|
+
recordFormStart(fieldId) {
|
|
295
|
+
const existing = this.data.formEvents.find((e) => e.fieldId === fieldId && e.endTime === 0);
|
|
296
|
+
if (!existing) {
|
|
297
|
+
this.data.formEvents.push({ fieldId, startTime: Date.now(), endTime: 0 });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Record form field interaction end
|
|
302
|
+
*/
|
|
303
|
+
recordFormEnd(fieldId) {
|
|
304
|
+
const event = this.data.formEvents.find((e) => e.fieldId === fieldId && e.endTime === 0);
|
|
305
|
+
if (event) {
|
|
306
|
+
event.endTime = Date.now();
|
|
307
|
+
}
|
|
308
|
+
this.checkAndClassify();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Record focus/blur event
|
|
312
|
+
*/
|
|
313
|
+
recordFocusBlur(type) {
|
|
314
|
+
this.data.focusBlurEvents.push({ type, time: Date.now() });
|
|
315
|
+
if (this.data.focusBlurEvents.length > 20) {
|
|
316
|
+
this.data.focusBlurEvents = this.data.focusBlurEvents.slice(-20);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check if we have enough data and classify
|
|
321
|
+
*/
|
|
322
|
+
checkAndClassify() {
|
|
323
|
+
if (this.classified) return;
|
|
324
|
+
const sessionDuration = Date.now() - this.data.startTime;
|
|
325
|
+
if (sessionDuration < this.minSessionTime) return;
|
|
326
|
+
const hasData = this.data.scrollEvents.length >= 2 || this.data.mouseEvents.length >= 5 || this.data.firstClickTime !== null;
|
|
327
|
+
if (!hasData) return;
|
|
328
|
+
this.classify();
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Force classification (for beforeunload)
|
|
332
|
+
*/
|
|
333
|
+
forceClassify() {
|
|
334
|
+
if (this.classified) return this.result;
|
|
335
|
+
return this.classify();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Perform classification
|
|
339
|
+
*/
|
|
340
|
+
classify() {
|
|
341
|
+
const sessionDuration = Date.now() - this.data.startTime;
|
|
342
|
+
const signals = this.extractSignals();
|
|
343
|
+
let humanLogProb = Math.log(PRIORS.human);
|
|
344
|
+
let aiLogProb = Math.log(PRIORS.ai_influenced);
|
|
345
|
+
for (const signal of signals) {
|
|
346
|
+
const humanWeight = NAIVE_BAYES_WEIGHTS.human[signal] ?? DEFAULT_WEIGHT;
|
|
347
|
+
const aiWeight = NAIVE_BAYES_WEIGHTS.ai_influenced[signal] ?? DEFAULT_WEIGHT;
|
|
348
|
+
humanLogProb += Math.log(humanWeight);
|
|
349
|
+
aiLogProb += Math.log(aiWeight);
|
|
350
|
+
}
|
|
351
|
+
const maxLog = Math.max(humanLogProb, aiLogProb);
|
|
352
|
+
const humanExp = Math.exp(humanLogProb - maxLog);
|
|
353
|
+
const aiExp = Math.exp(aiLogProb - maxLog);
|
|
354
|
+
const total = humanExp + aiExp;
|
|
355
|
+
const humanProbability = humanExp / total;
|
|
356
|
+
const aiProbability = aiExp / total;
|
|
357
|
+
let classification;
|
|
358
|
+
let confidence;
|
|
359
|
+
if (humanProbability > 0.6) {
|
|
360
|
+
classification = "human";
|
|
361
|
+
confidence = humanProbability;
|
|
362
|
+
} else if (aiProbability > 0.6) {
|
|
363
|
+
classification = "ai_influenced";
|
|
364
|
+
confidence = aiProbability;
|
|
365
|
+
} else {
|
|
366
|
+
classification = "uncertain";
|
|
367
|
+
confidence = Math.max(humanProbability, aiProbability);
|
|
368
|
+
}
|
|
369
|
+
this.result = {
|
|
370
|
+
classification,
|
|
371
|
+
humanProbability,
|
|
372
|
+
aiProbability,
|
|
373
|
+
confidence,
|
|
374
|
+
signals,
|
|
375
|
+
timestamp: Date.now(),
|
|
376
|
+
sessionDurationMs: sessionDuration
|
|
377
|
+
};
|
|
378
|
+
this.classified = true;
|
|
379
|
+
if (this.onClassify) {
|
|
380
|
+
this.onClassify(this.result);
|
|
381
|
+
}
|
|
382
|
+
return this.result;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Extract behavioral signals from collected data
|
|
386
|
+
*/
|
|
387
|
+
extractSignals() {
|
|
388
|
+
const signals = [];
|
|
389
|
+
if (this.data.firstClickTime !== null) {
|
|
390
|
+
const timeToClick = this.data.firstClickTime - this.data.startTime;
|
|
391
|
+
if (timeToClick < 500) {
|
|
392
|
+
signals.push("time_to_first_click_immediate");
|
|
393
|
+
} else if (timeToClick < 2e3) {
|
|
394
|
+
signals.push("time_to_first_click_fast");
|
|
395
|
+
} else if (timeToClick < 1e4) {
|
|
396
|
+
signals.push("time_to_first_click_normal");
|
|
397
|
+
} else {
|
|
398
|
+
signals.push("time_to_first_click_delayed");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (this.data.scrollEvents.length === 0) {
|
|
402
|
+
signals.push("scroll_speed_none");
|
|
403
|
+
} else if (this.data.scrollEvents.length >= 3) {
|
|
404
|
+
const scrollDeltas = [];
|
|
405
|
+
for (let i = 1; i < this.data.scrollEvents.length; i++) {
|
|
406
|
+
const delta = this.data.scrollEvents[i].time - this.data.scrollEvents[i - 1].time;
|
|
407
|
+
scrollDeltas.push(delta);
|
|
408
|
+
}
|
|
409
|
+
const mean = scrollDeltas.reduce((a, b) => a + b, 0) / scrollDeltas.length;
|
|
410
|
+
const variance = scrollDeltas.reduce((sum, d) => sum + Math.pow(d - mean, 2), 0) / scrollDeltas.length;
|
|
411
|
+
const stdDev = Math.sqrt(variance);
|
|
412
|
+
const cv = mean > 0 ? stdDev / mean : 0;
|
|
413
|
+
if (cv < 0.2) {
|
|
414
|
+
signals.push("scroll_speed_uniform");
|
|
415
|
+
} else if (cv < 0.6) {
|
|
416
|
+
signals.push("scroll_speed_variable");
|
|
417
|
+
} else {
|
|
418
|
+
signals.push("scroll_speed_erratic");
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (this.data.mouseEvents.length === 0) {
|
|
422
|
+
signals.push("mouse_movement_none");
|
|
423
|
+
} else if (this.data.mouseEvents.length >= 10) {
|
|
424
|
+
const n = Math.min(this.data.mouseEvents.length, 20);
|
|
425
|
+
const recentMouse = this.data.mouseEvents.slice(-n);
|
|
426
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
|
427
|
+
for (const event of recentMouse) {
|
|
428
|
+
sumX += event.x;
|
|
429
|
+
sumY += event.y;
|
|
430
|
+
sumXY += event.x * event.y;
|
|
431
|
+
sumX2 += event.x * event.x;
|
|
432
|
+
}
|
|
433
|
+
const denominator = n * sumX2 - sumX * sumX;
|
|
434
|
+
const slope = denominator !== 0 ? (n * sumXY - sumX * sumY) / denominator : 0;
|
|
435
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
436
|
+
let ssRes = 0, ssTot = 0;
|
|
437
|
+
const yMean = sumY / n;
|
|
438
|
+
for (const event of recentMouse) {
|
|
439
|
+
const yPred = slope * event.x + intercept;
|
|
440
|
+
ssRes += Math.pow(event.y - yPred, 2);
|
|
441
|
+
ssTot += Math.pow(event.y - yMean, 2);
|
|
442
|
+
}
|
|
443
|
+
const r2 = ssTot !== 0 ? 1 - ssRes / ssTot : 0;
|
|
444
|
+
if (r2 > 0.95) {
|
|
445
|
+
signals.push("mouse_movement_linear");
|
|
446
|
+
} else {
|
|
447
|
+
signals.push("mouse_movement_curved");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const completedForms = this.data.formEvents.filter((e) => e.endTime > 0);
|
|
451
|
+
if (completedForms.length > 0) {
|
|
452
|
+
const avgFillTime = completedForms.reduce((sum, e) => sum + (e.endTime - e.startTime), 0) / completedForms.length;
|
|
453
|
+
if (avgFillTime < 100) {
|
|
454
|
+
signals.push("form_fill_instant");
|
|
455
|
+
} else if (avgFillTime < 500) {
|
|
456
|
+
signals.push("form_fill_fast");
|
|
457
|
+
} else {
|
|
458
|
+
signals.push("form_fill_normal");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (this.data.focusBlurEvents.length >= 4) {
|
|
462
|
+
const recentFB = this.data.focusBlurEvents.slice(-10);
|
|
463
|
+
const intervals = [];
|
|
464
|
+
for (let i = 1; i < recentFB.length; i++) {
|
|
465
|
+
intervals.push(recentFB[i].time - recentFB[i - 1].time);
|
|
466
|
+
}
|
|
467
|
+
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
|
|
468
|
+
if (avgInterval < 1e3) {
|
|
469
|
+
signals.push("focus_blur_rapid");
|
|
470
|
+
} else {
|
|
471
|
+
signals.push("focus_blur_normal");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return signals;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Add context signals (set by tracker from external data)
|
|
478
|
+
*/
|
|
479
|
+
addContextSignal(_signal) {
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get current result (null if not yet classified)
|
|
483
|
+
*/
|
|
484
|
+
getResult() {
|
|
485
|
+
return this.result;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Check if classification has been performed
|
|
489
|
+
*/
|
|
490
|
+
hasClassified() {
|
|
491
|
+
return this.classified;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
179
495
|
// src/utils.ts
|
|
180
496
|
function generateUUID() {
|
|
181
497
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
@@ -261,6 +577,8 @@ var sessionId = null;
|
|
|
261
577
|
var sessionStartTime = null;
|
|
262
578
|
var navigationTiming = null;
|
|
263
579
|
var aiDetection = null;
|
|
580
|
+
var behavioralClassifier = null;
|
|
581
|
+
var behavioralMLResult = null;
|
|
264
582
|
function log(...args) {
|
|
265
583
|
if (debugMode) {
|
|
266
584
|
console.log("[Loamly]", ...args);
|
|
@@ -300,6 +618,9 @@ function init(userConfig = {}) {
|
|
|
300
618
|
if (!userConfig.disableBehavioral) {
|
|
301
619
|
setupBehavioralTracking();
|
|
302
620
|
}
|
|
621
|
+
behavioralClassifier = new BehavioralClassifier(1e4);
|
|
622
|
+
behavioralClassifier.setOnClassify(handleBehavioralClassification);
|
|
623
|
+
setupBehavioralMLTracking();
|
|
303
624
|
log("Initialization complete");
|
|
304
625
|
}
|
|
305
626
|
function pageview(customUrl) {
|
|
@@ -481,6 +802,89 @@ function sendBehavioralEvent(eventType, data) {
|
|
|
481
802
|
body: JSON.stringify(payload)
|
|
482
803
|
});
|
|
483
804
|
}
|
|
805
|
+
function setupBehavioralMLTracking() {
|
|
806
|
+
if (!behavioralClassifier) return;
|
|
807
|
+
let mouseSampleCount = 0;
|
|
808
|
+
document.addEventListener("mousemove", (e) => {
|
|
809
|
+
mouseSampleCount++;
|
|
810
|
+
if (mouseSampleCount % 10 === 0 && behavioralClassifier) {
|
|
811
|
+
behavioralClassifier.recordMouse(e.clientX, e.clientY);
|
|
812
|
+
}
|
|
813
|
+
}, { passive: true });
|
|
814
|
+
document.addEventListener("click", () => {
|
|
815
|
+
if (behavioralClassifier) {
|
|
816
|
+
behavioralClassifier.recordClick();
|
|
817
|
+
}
|
|
818
|
+
}, { passive: true });
|
|
819
|
+
let lastScrollY = 0;
|
|
820
|
+
document.addEventListener("scroll", () => {
|
|
821
|
+
const currentY = window.scrollY;
|
|
822
|
+
if (Math.abs(currentY - lastScrollY) > 50 && behavioralClassifier) {
|
|
823
|
+
lastScrollY = currentY;
|
|
824
|
+
behavioralClassifier.recordScroll(currentY);
|
|
825
|
+
}
|
|
826
|
+
}, { passive: true });
|
|
827
|
+
document.addEventListener("focusin", (e) => {
|
|
828
|
+
if (behavioralClassifier) {
|
|
829
|
+
behavioralClassifier.recordFocusBlur("focus");
|
|
830
|
+
const target = e.target;
|
|
831
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
|
|
832
|
+
behavioralClassifier.recordFormStart(target.id || target.getAttribute("name") || "unknown");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}, { passive: true });
|
|
836
|
+
document.addEventListener("focusout", (e) => {
|
|
837
|
+
if (behavioralClassifier) {
|
|
838
|
+
behavioralClassifier.recordFocusBlur("blur");
|
|
839
|
+
const target = e.target;
|
|
840
|
+
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
|
|
841
|
+
behavioralClassifier.recordFormEnd(target.id || target.getAttribute("name") || "unknown");
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}, { passive: true });
|
|
845
|
+
window.addEventListener("beforeunload", () => {
|
|
846
|
+
if (behavioralClassifier && !behavioralClassifier.hasClassified()) {
|
|
847
|
+
const result = behavioralClassifier.forceClassify();
|
|
848
|
+
if (result) {
|
|
849
|
+
handleBehavioralClassification(result);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
setTimeout(() => {
|
|
854
|
+
if (behavioralClassifier && !behavioralClassifier.hasClassified()) {
|
|
855
|
+
behavioralClassifier.forceClassify();
|
|
856
|
+
}
|
|
857
|
+
}, 3e4);
|
|
858
|
+
}
|
|
859
|
+
function handleBehavioralClassification(result) {
|
|
860
|
+
log("Behavioral ML classification:", result);
|
|
861
|
+
behavioralMLResult = {
|
|
862
|
+
classification: result.classification,
|
|
863
|
+
humanProbability: result.humanProbability,
|
|
864
|
+
aiProbability: result.aiProbability,
|
|
865
|
+
confidence: result.confidence,
|
|
866
|
+
signals: result.signals,
|
|
867
|
+
sessionDurationMs: result.sessionDurationMs
|
|
868
|
+
};
|
|
869
|
+
sendBehavioralEvent("ml_classification", {
|
|
870
|
+
classification: result.classification,
|
|
871
|
+
human_probability: result.humanProbability,
|
|
872
|
+
ai_probability: result.aiProbability,
|
|
873
|
+
confidence: result.confidence,
|
|
874
|
+
signals: result.signals,
|
|
875
|
+
session_duration_ms: result.sessionDurationMs,
|
|
876
|
+
navigation_timing: navigationTiming,
|
|
877
|
+
ai_detection: aiDetection
|
|
878
|
+
});
|
|
879
|
+
if (result.classification === "ai_influenced" && result.confidence >= 0.7) {
|
|
880
|
+
aiDetection = {
|
|
881
|
+
isAI: true,
|
|
882
|
+
confidence: result.confidence,
|
|
883
|
+
method: "behavioral"
|
|
884
|
+
};
|
|
885
|
+
log("AI detection updated from behavioral ML:", aiDetection);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
484
888
|
function getCurrentSessionId() {
|
|
485
889
|
return sessionId;
|
|
486
890
|
}
|
|
@@ -493,6 +897,9 @@ function getAIDetectionResult() {
|
|
|
493
897
|
function getNavigationTimingResult() {
|
|
494
898
|
return navigationTiming;
|
|
495
899
|
}
|
|
900
|
+
function getBehavioralMLResult() {
|
|
901
|
+
return behavioralMLResult;
|
|
902
|
+
}
|
|
496
903
|
function isTrackerInitialized() {
|
|
497
904
|
return initialized;
|
|
498
905
|
}
|
|
@@ -504,6 +911,8 @@ function reset() {
|
|
|
504
911
|
sessionStartTime = null;
|
|
505
912
|
navigationTiming = null;
|
|
506
913
|
aiDetection = null;
|
|
914
|
+
behavioralClassifier = null;
|
|
915
|
+
behavioralMLResult = null;
|
|
507
916
|
try {
|
|
508
917
|
sessionStorage.removeItem("loamly_session");
|
|
509
918
|
sessionStorage.removeItem("loamly_start");
|
|
@@ -524,6 +933,7 @@ var loamly = {
|
|
|
524
933
|
getVisitorId: getCurrentVisitorId,
|
|
525
934
|
getAIDetection: getAIDetectionResult,
|
|
526
935
|
getNavigationTiming: getNavigationTimingResult,
|
|
936
|
+
getBehavioralML: getBehavioralMLResult,
|
|
527
937
|
isInitialized: isTrackerInitialized,
|
|
528
938
|
reset,
|
|
529
939
|
debug: setDebug
|