@keverdjs/fraud-sdk-react 1.0.0 → 1.1.2

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.mjs CHANGED
@@ -272,247 +272,363 @@ var KeverdDeviceCollector = class {
272
272
  }
273
273
  };
274
274
 
275
- // src/collectors/keverd-behavioral-collector.ts
276
- var KeverdBehavioralCollector = class {
277
- constructor() {
278
- this.keystrokes = [];
279
- this.mouseMovements = [];
280
- this.lastKeyDownTime = /* @__PURE__ */ new Map();
275
+ // src/collectors/kinematic-engine.ts
276
+ var VELOCITY_SPIKE_THRESHOLD = 2.5;
277
+ var WINDOW_LIMIT = 200;
278
+ var KinematicEngine = class {
279
+ constructor(options = {}) {
280
+ this.featureVectors = [];
281
+ this.pointerQueue = [];
282
+ this.lastVelocity = 0;
283
+ this.lastAcceleration = 0;
284
+ this.lastAngle = 0;
285
+ this.lastPoint = null;
286
+ this.secondLastPoint = null;
287
+ this.rafId = null;
288
+ this.maxSwipeVelocity = 0;
289
+ this.isActive = false;
290
+ this.mouseMoveHandler = (event) => this.enqueuePoint(event, "move");
291
+ this.mouseDownHandler = (event) => this.enqueuePoint(event, "down");
292
+ this.mouseUpHandler = (event) => this.enqueuePoint(event, "up");
293
+ this.onEvent = options.onEvent;
294
+ this.maskValue = options.maskValue ?? -1;
295
+ }
296
+ start() {
297
+ if (this.isActive || typeof document === "undefined") return;
298
+ document.addEventListener("mousemove", this.mouseMoveHandler, { passive: true });
299
+ document.addEventListener("mousedown", this.mouseDownHandler, { passive: true });
300
+ document.addEventListener("mouseup", this.mouseUpHandler, { passive: true });
301
+ this.isActive = true;
302
+ }
303
+ stop() {
304
+ if (!this.isActive || typeof document === "undefined") return;
305
+ document.removeEventListener("mousemove", this.mouseMoveHandler);
306
+ document.removeEventListener("mousedown", this.mouseDownHandler);
307
+ document.removeEventListener("mouseup", this.mouseUpHandler);
308
+ if (this.rafId !== null) {
309
+ cancelAnimationFrame(this.rafId);
310
+ this.rafId = null;
311
+ }
312
+ this.isActive = false;
313
+ }
314
+ getVectors() {
315
+ return this.featureVectors;
316
+ }
317
+ getSuspiciousSwipeVelocity() {
318
+ return this.maxSwipeVelocity;
319
+ }
320
+ reset() {
321
+ this.featureVectors = [];
322
+ this.pointerQueue = [];
323
+ this.lastVelocity = 0;
324
+ this.lastAcceleration = 0;
325
+ this.lastAngle = 0;
326
+ this.lastPoint = null;
327
+ this.secondLastPoint = null;
328
+ this.maxSwipeVelocity = 0;
329
+ }
330
+ enqueuePoint(event, type) {
331
+ const point = {
332
+ x: event.clientX,
333
+ y: event.clientY,
334
+ timestamp: performance.now(),
335
+ type
336
+ };
337
+ this.pointerQueue.push(point);
338
+ if (this.pointerQueue.length > WINDOW_LIMIT) {
339
+ this.pointerQueue.shift();
340
+ }
341
+ if (this.onEvent) {
342
+ this.onEvent(type === "move" ? "mousemove" : type === "down" ? "mousedown" : "mouseup");
343
+ }
344
+ this.scheduleProcess();
345
+ }
346
+ scheduleProcess() {
347
+ if (this.rafId !== null) return;
348
+ this.rafId = requestAnimationFrame(() => {
349
+ this.rafId = null;
350
+ this.processQueue();
351
+ if (this.pointerQueue.length > 0) {
352
+ this.scheduleProcess();
353
+ }
354
+ });
355
+ }
356
+ processQueue() {
357
+ while (this.pointerQueue.length > 0) {
358
+ const point = this.pointerQueue.shift();
359
+ this.computeFeatures(point);
360
+ this.secondLastPoint = this.lastPoint;
361
+ this.lastPoint = point;
362
+ }
363
+ }
364
+ computeFeatures(point) {
365
+ if (!this.lastPoint) {
366
+ this.lastPoint = point;
367
+ this.lastAngle = 0;
368
+ return;
369
+ }
370
+ const dt = point.timestamp - this.lastPoint.timestamp;
371
+ if (dt <= 0) {
372
+ return;
373
+ }
374
+ const dx = point.x - this.lastPoint.x;
375
+ const dy = point.y - this.lastPoint.y;
376
+ const distance = Math.sqrt(dx * dx + dy * dy);
377
+ const velocity = distance / dt;
378
+ const acceleration = (velocity - this.lastVelocity) / dt;
379
+ const jerk = (acceleration - this.lastAcceleration) / dt;
380
+ const angle = Math.atan2(dy, dx);
381
+ const angularVelocity = this.normalizeAngle(angle - this.lastAngle) / dt;
382
+ const curvature = this.calculateCurvature(point);
383
+ this.featureVectors.push({
384
+ timestamp: point.timestamp,
385
+ velocity,
386
+ acceleration,
387
+ jerk,
388
+ curvature,
389
+ angularVelocity
390
+ });
391
+ this.maxSwipeVelocity = Math.max(this.maxSwipeVelocity, velocity);
392
+ if (velocity > VELOCITY_SPIKE_THRESHOLD) {
393
+ this.featureVectors.push({
394
+ timestamp: point.timestamp,
395
+ velocity,
396
+ acceleration,
397
+ jerk,
398
+ curvature,
399
+ angularVelocity: angularVelocity * 1.2
400
+ });
401
+ }
402
+ if (this.featureVectors.length > WINDOW_LIMIT) {
403
+ this.featureVectors.splice(0, this.featureVectors.length - WINDOW_LIMIT);
404
+ }
405
+ this.lastVelocity = velocity;
406
+ this.lastAcceleration = acceleration;
407
+ this.lastAngle = angle;
408
+ }
409
+ calculateCurvature(point) {
410
+ if (!this.lastPoint || !this.secondLastPoint) return 0;
411
+ const p0 = this.secondLastPoint;
412
+ const p1 = this.lastPoint;
413
+ const p2 = point;
414
+ const chordLength = Math.hypot(p2.x - p0.x, p2.y - p0.y);
415
+ const pathLength = Math.hypot(p1.x - p0.x, p1.y - p0.y) + Math.hypot(p2.x - p1.x, p2.y - p1.y);
416
+ if (chordLength === 0) return 0;
417
+ return (pathLength - chordLength) / chordLength;
418
+ }
419
+ normalizeAngle(angleDiff) {
420
+ while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
421
+ while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
422
+ return angleDiff;
423
+ }
424
+ };
425
+
426
+ // src/collectors/keystroke-monitor.ts
427
+ var MAX_BUFFER = 200;
428
+ var KeystrokeMonitor = class {
429
+ constructor(options = {}) {
430
+ this.keyDownTimes = /* @__PURE__ */ new Map();
281
431
  this.lastKeyUpTime = null;
432
+ this.lastKeyDownTime = null;
433
+ this.featureVectors = [];
434
+ this.dwellTimes = [];
435
+ this.flightTimes = [];
436
+ this.digraphLatencies = [];
282
437
  this.isActive = false;
283
- this.sessionStartTime = Date.now();
284
- this.typingDwellTimes = [];
285
- this.typingFlightTimes = [];
286
- this.swipeVelocities = [];
287
- this.sessionEvents = [];
288
- this.touchStartPositions = /* @__PURE__ */ new Map();
289
438
  this.keyDownHandler = (event) => this.handleKeyDown(event);
290
439
  this.keyUpHandler = (event) => this.handleKeyUp(event);
291
- this.mouseMoveHandler = (event) => this.handleMouseMove(event);
292
- this.touchStartHandler = (event) => this.handleTouchStart(event);
293
- this.touchEndHandler = (event) => this.handleTouchEnd(event);
440
+ this.onEvent = options.onEvent;
294
441
  }
295
- /**
296
- * Start collecting behavioral data
297
- */
298
442
  start() {
299
- if (this.isActive) return;
300
- if (typeof document !== "undefined") {
301
- document.addEventListener("keydown", this.keyDownHandler, { passive: true });
302
- document.addEventListener("keyup", this.keyUpHandler, { passive: true });
303
- document.addEventListener("mousemove", this.mouseMoveHandler, { passive: true });
304
- document.addEventListener("touchstart", this.touchStartHandler, { passive: true });
305
- document.addEventListener("touchend", this.touchEndHandler, { passive: true });
306
- }
443
+ if (this.isActive || typeof document === "undefined") return;
444
+ document.addEventListener("keydown", this.keyDownHandler, { passive: true, capture: true });
445
+ document.addEventListener("keyup", this.keyUpHandler, { passive: true, capture: true });
307
446
  this.isActive = true;
308
- this.sessionStartTime = Date.now();
309
447
  }
310
- /**
311
- * Stop collecting behavioral data
312
- */
313
448
  stop() {
314
- if (!this.isActive) return;
315
- if (typeof document !== "undefined") {
316
- document.removeEventListener("keydown", this.keyDownHandler);
317
- document.removeEventListener("keyup", this.keyUpHandler);
318
- document.removeEventListener("mousemove", this.mouseMoveHandler);
319
- document.removeEventListener("touchstart", this.touchStartHandler);
320
- document.removeEventListener("touchend", this.touchEndHandler);
321
- }
449
+ if (!this.isActive || typeof document === "undefined") return;
450
+ document.removeEventListener("keydown", this.keyDownHandler, true);
451
+ document.removeEventListener("keyup", this.keyUpHandler, true);
322
452
  this.isActive = false;
323
453
  }
324
- /**
325
- * Get collected behavioral data
326
- */
327
- getData() {
328
- const dwellTimes = this.typingDwellTimes.slice(-20);
329
- const flightTimes = this.typingFlightTimes.slice(-20);
330
- const avgSwipeVelocity = this.swipeVelocities.length > 0 ? this.swipeVelocities.reduce((a, b) => a + b, 0) / this.swipeVelocities.length : 0;
331
- const sessionEntropy = this.calculateSessionEntropy();
332
- return {
333
- typing_dwell_ms: dwellTimes.length > 0 ? dwellTimes : [],
334
- typing_flight_ms: flightTimes.length > 0 ? flightTimes : [],
335
- swipe_velocity: avgSwipeVelocity > 0 ? avgSwipeVelocity : 0,
336
- session_entropy: sessionEntropy >= 0 ? sessionEntropy : 0
337
- };
454
+ getVectors() {
455
+ return this.featureVectors;
456
+ }
457
+ getDwellTimes(limit = 50) {
458
+ return this.dwellTimes.slice(-limit);
459
+ }
460
+ getFlightTimes(limit = 50) {
461
+ return this.flightTimes.slice(-limit);
338
462
  }
339
- /**
340
- * Reset collected data
341
- */
342
463
  reset() {
343
- this.keystrokes = [];
344
- this.mouseMovements = [];
345
- this.lastKeyDownTime.clear();
464
+ this.keyDownTimes.clear();
346
465
  this.lastKeyUpTime = null;
347
- this.typingDwellTimes = [];
348
- this.typingFlightTimes = [];
349
- this.swipeVelocities = [];
350
- this.sessionEvents = [];
351
- this.touchStartPositions.clear();
352
- this.sessionStartTime = Date.now();
466
+ this.lastKeyDownTime = null;
467
+ this.featureVectors = [];
468
+ this.dwellTimes = [];
469
+ this.flightTimes = [];
470
+ this.digraphLatencies = [];
353
471
  }
354
- /**
355
- * Handle keydown event
356
- */
357
472
  handleKeyDown(event) {
358
- if (this.shouldIgnoreKey(event)) return;
473
+ if (!this.isTargetField(event)) return;
359
474
  const now = performance.now();
360
- this.lastKeyDownTime.set(event.key, now);
361
- const keystroke = {
362
- key: event.key,
363
- timestamp: now,
364
- type: "keydown"
365
- };
366
- this.keystrokes.push(keystroke);
367
- this.sessionEvents.push({ type: "keydown", timestamp: now });
475
+ if (this.lastKeyUpTime !== null) {
476
+ const flight = now - this.lastKeyUpTime;
477
+ this.flightTimes.push(flight);
478
+ this.appendVector({ dwellTime: -1, flightTime: flight, digraphLatency: -1, timestamp: now });
479
+ }
480
+ if (this.lastKeyDownTime !== null) {
481
+ const digraphLatency = now - this.lastKeyDownTime;
482
+ this.digraphLatencies.push(digraphLatency);
483
+ this.appendVector({ dwellTime: -1, flightTime: -1, digraphLatency, timestamp: now });
484
+ }
485
+ this.lastKeyDownTime = now;
486
+ this.keyDownTimes.set(event.code, now);
487
+ if (this.onEvent) this.onEvent("keydown");
368
488
  }
369
- /**
370
- * Handle keyup event
371
- */
372
489
  handleKeyUp(event) {
373
- if (this.shouldIgnoreKey(event)) return;
490
+ if (!this.isTargetField(event)) return;
374
491
  const now = performance.now();
375
- const keyDownTime = this.lastKeyDownTime.get(event.key);
376
- if (keyDownTime !== void 0) {
377
- const dwellTime = now - keyDownTime;
378
- this.typingDwellTimes.push(dwellTime);
379
- this.lastKeyDownTime.delete(event.key);
380
- }
381
- if (this.lastKeyUpTime !== null) {
382
- const flightTime = now - this.lastKeyUpTime;
383
- this.typingFlightTimes.push(flightTime);
492
+ const start = this.keyDownTimes.get(event.code);
493
+ if (start !== void 0) {
494
+ const dwell = now - start;
495
+ this.dwellTimes.push(dwell);
496
+ this.appendVector({ dwellTime: dwell, flightTime: -1, digraphLatency: -1, timestamp: now });
497
+ this.keyDownTimes.delete(event.code);
384
498
  }
385
499
  this.lastKeyUpTime = now;
386
- const keystroke = {
387
- key: event.key,
388
- timestamp: now,
389
- type: "keyup"
390
- };
391
- this.keystrokes.push(keystroke);
392
- this.sessionEvents.push({ type: "keyup", timestamp: now });
500
+ if (this.onEvent) this.onEvent("keyup");
393
501
  }
394
- /**
395
- * Handle mouse move event
396
- */
397
- handleMouseMove(event) {
398
- const mouseEvent = {
399
- x: event.clientX,
400
- y: event.clientY,
401
- timestamp: performance.now(),
402
- type: "move"
403
- };
404
- if (this.mouseMovements.length > 0) {
405
- const lastEvent = this.mouseMovements[this.mouseMovements.length - 1];
406
- const timeDelta = mouseEvent.timestamp - lastEvent.timestamp;
407
- if (timeDelta > 0) {
408
- const distance = Math.sqrt(
409
- Math.pow(mouseEvent.x - lastEvent.x, 2) + Math.pow(mouseEvent.y - lastEvent.y, 2)
410
- );
411
- mouseEvent.velocity = distance / timeDelta;
412
- }
502
+ appendVector(vector) {
503
+ this.featureVectors.push(vector);
504
+ if (this.featureVectors.length > MAX_BUFFER) {
505
+ this.featureVectors.splice(0, this.featureVectors.length - MAX_BUFFER);
413
506
  }
414
- this.mouseMovements.push(mouseEvent);
415
- this.sessionEvents.push({ type: "mousemove", timestamp: mouseEvent.timestamp });
416
507
  }
417
- /**
418
- * Handle touch start (for swipe detection)
419
- */
420
- handleTouchStart(event) {
421
- const now = performance.now();
422
- for (let i = 0; i < event.touches.length; i++) {
423
- const touch = event.touches[i];
424
- this.touchStartPositions.set(touch.identifier, {
425
- x: touch.clientX,
426
- y: touch.clientY,
427
- timestamp: now
428
- });
429
- }
430
- this.sessionEvents.push({
431
- type: "touchstart",
432
- timestamp: now
433
- });
508
+ isTargetField(event) {
509
+ const target = event.target;
510
+ if (!target) return false;
511
+ const isEditable = target.isContentEditable || ["INPUT", "TEXTAREA"].includes(target.tagName);
512
+ return isEditable;
434
513
  }
435
- /**
436
- * Handle touch end (calculate swipe velocity)
437
- */
438
- handleTouchEnd(event) {
439
- const now = performance.now();
440
- for (let i = 0; i < event.changedTouches.length; i++) {
441
- const touch = event.changedTouches[i];
442
- const startPos = this.touchStartPositions.get(touch.identifier);
443
- if (startPos) {
444
- const timeDelta = now - startPos.timestamp;
445
- if (timeDelta > 0 && timeDelta < 1e3) {
446
- const distance = Math.sqrt(
447
- Math.pow(touch.clientX - startPos.x, 2) + Math.pow(touch.clientY - startPos.y, 2)
448
- );
449
- if (distance > 10) {
450
- const velocity = distance / timeDelta;
451
- this.swipeVelocities.push(velocity);
452
- }
453
- }
454
- this.touchStartPositions.delete(touch.identifier);
514
+ };
515
+
516
+ // src/collectors/keverd-behavioral-collector.ts
517
+ var WINDOW_SIZE = 50;
518
+ var MASK_VALUE = -1;
519
+ var FEATURE_NAMES = [
520
+ "velocity",
521
+ "acceleration",
522
+ "jerk",
523
+ "curvature",
524
+ "angularVelocity",
525
+ "dwell",
526
+ "flight",
527
+ "digraphLatency"
528
+ ];
529
+ var KeverdBehavioralCollector = class {
530
+ constructor() {
531
+ this.isActive = false;
532
+ this.sessionEvents = [];
533
+ this.trackEvent = (event) => {
534
+ this.sessionEvents.push({ type: event, timestamp: performance.now() });
535
+ if (this.sessionEvents.length > 500) {
536
+ this.sessionEvents.shift();
455
537
  }
538
+ };
539
+ this.kinematicEngine = new KinematicEngine({ onEvent: this.trackEvent });
540
+ this.keystrokeMonitor = new KeystrokeMonitor({ onEvent: this.trackEvent });
541
+ }
542
+ start() {
543
+ if (this.isActive) return;
544
+ this.kinematicEngine.start();
545
+ this.keystrokeMonitor.start();
546
+ this.isActive = true;
547
+ }
548
+ stop() {
549
+ if (!this.isActive) return;
550
+ this.kinematicEngine.stop();
551
+ this.keystrokeMonitor.stop();
552
+ this.isActive = false;
553
+ }
554
+ reset() {
555
+ this.sessionEvents = [];
556
+ this.kinematicEngine.reset();
557
+ this.keystrokeMonitor.reset();
558
+ }
559
+ getData() {
560
+ const kinematicVectors = this.kinematicEngine.getVectors();
561
+ const keystrokeVectors = this.keystrokeMonitor.getVectors();
562
+ const merged = [
563
+ ...kinematicVectors.map((v) => ({
564
+ timestamp: v.timestamp,
565
+ values: [
566
+ v.velocity,
567
+ v.acceleration,
568
+ v.jerk,
569
+ v.curvature,
570
+ v.angularVelocity,
571
+ MASK_VALUE,
572
+ MASK_VALUE,
573
+ MASK_VALUE
574
+ ]
575
+ })),
576
+ ...keystrokeVectors.map((v) => ({
577
+ timestamp: v.timestamp,
578
+ values: [
579
+ MASK_VALUE,
580
+ MASK_VALUE,
581
+ MASK_VALUE,
582
+ MASK_VALUE,
583
+ MASK_VALUE,
584
+ v.dwellTime,
585
+ v.flightTime,
586
+ v.digraphLatency
587
+ ]
588
+ }))
589
+ ].sort((a, b) => a.timestamp - b.timestamp);
590
+ const sequence = this.buildSequence(merged);
591
+ const suspiciousSwipeVelocity = this.kinematicEngine.getSuspiciousSwipeVelocity();
592
+ const entropy = this.calculateSessionEntropy();
593
+ const dwellTimes = this.keystrokeMonitor.getDwellTimes();
594
+ const flightTimes = this.keystrokeMonitor.getFlightTimes();
595
+ return {
596
+ typing_dwell_ms: dwellTimes,
597
+ typing_flight_ms: flightTimes,
598
+ swipe_velocity: suspiciousSwipeVelocity,
599
+ suspicious_swipe_velocity: suspiciousSwipeVelocity,
600
+ session_entropy: entropy,
601
+ behavioral_vectors: sequence
602
+ };
603
+ }
604
+ buildSequence(vectors) {
605
+ const padded = [];
606
+ const startIndex = Math.max(0, vectors.length - WINDOW_SIZE);
607
+ const window2 = vectors.slice(startIndex);
608
+ for (const vector of window2) {
609
+ padded.push(vector.values);
456
610
  }
457
- this.sessionEvents.push({
458
- type: "touchend",
459
- timestamp: now
460
- });
611
+ while (padded.length < WINDOW_SIZE) {
612
+ padded.unshift(new Array(FEATURE_NAMES.length).fill(MASK_VALUE));
613
+ }
614
+ return {
615
+ featureNames: [...FEATURE_NAMES],
616
+ windowSize: WINDOW_SIZE,
617
+ maskValue: MASK_VALUE,
618
+ sequence: padded
619
+ };
461
620
  }
462
- /**
463
- * Calculate session entropy based on event diversity
464
- */
465
621
  calculateSessionEntropy() {
466
622
  if (this.sessionEvents.length === 0) return 0;
467
- const eventTypeCounts = {};
468
- for (const event of this.sessionEvents) {
469
- eventTypeCounts[event.type] = (eventTypeCounts[event.type] || 0) + 1;
470
- }
471
- const totalEvents = this.sessionEvents.length;
472
- let entropy = 0;
473
- for (const count of Object.values(eventTypeCounts)) {
474
- const probability = count / totalEvents;
475
- if (probability > 0) {
476
- entropy -= probability * Math.log2(probability);
477
- }
478
- }
479
- return entropy;
480
- }
481
- /**
482
- * Check if key should be ignored
483
- */
484
- shouldIgnoreKey(event) {
485
- const ignoredKeys = [
486
- "Shift",
487
- "Control",
488
- "Alt",
489
- "Meta",
490
- "CapsLock",
491
- "Tab",
492
- "Escape",
493
- "Enter",
494
- "ArrowLeft",
495
- "ArrowRight",
496
- "ArrowUp",
497
- "ArrowDown",
498
- "Home",
499
- "End",
500
- "PageUp",
501
- "PageDown",
502
- "F1",
503
- "F2",
504
- "F3",
505
- "F4",
506
- "F5",
507
- "F6",
508
- "F7",
509
- "F8",
510
- "F9",
511
- "F10",
512
- "F11",
513
- "F12"
514
- ];
515
- return ignoredKeys.includes(event.key);
623
+ const counts = {};
624
+ this.sessionEvents.forEach((evt) => {
625
+ counts[evt.type] = (counts[evt.type] || 0) + 1;
626
+ });
627
+ const total = this.sessionEvents.length;
628
+ return Object.values(counts).reduce((entropy, count) => {
629
+ const probability = count / total;
630
+ return probability > 0 ? entropy - probability * Math.log2(probability) : entropy;
631
+ }, 0);
516
632
  }
517
633
  };
518
634
 
@@ -539,7 +655,6 @@ var KeverdSDK = class {
539
655
  throw new Error("Keverd SDK: apiKey is required");
540
656
  }
541
657
  this.config = {
542
- endpoint: config.endpoint || this.getDefaultEndpoint(),
543
658
  debug: false,
544
659
  ...config
545
660
  };
@@ -547,10 +662,10 @@ var KeverdSDK = class {
547
662
  this.sessionId = this.generateSessionId();
548
663
  this.isInitialized = true;
549
664
  if (this.config.debug) {
550
- console.log("[Keverd SDK] Initialized successfully", {
551
- endpoint: this.config.endpoint
552
- });
665
+ console.log("[Keverd SDK] Initialized successfully");
553
666
  }
667
+ this.startSession().catch(() => {
668
+ });
554
669
  }
555
670
  /**
556
671
  * Get visitor data (fingerprint and risk assessment)
@@ -585,6 +700,17 @@ var KeverdSDK = class {
585
700
  throw keverdError;
586
701
  }
587
702
  }
703
+ /**
704
+ * Extract origin and referrer from browser
705
+ */
706
+ getOriginHeaders() {
707
+ if (typeof window === "undefined") {
708
+ return {};
709
+ }
710
+ const origin = window.location.origin || void 0;
711
+ const referrer = document.referrer || void 0;
712
+ return { origin, referrer };
713
+ }
588
714
  /**
589
715
  * Send fingerprint request to backend
590
716
  */
@@ -592,13 +718,19 @@ var KeverdSDK = class {
592
718
  if (!this.config) {
593
719
  throw new Error("SDK not initialized");
594
720
  }
595
- const endpoint = this.config.endpoint || this.getDefaultEndpoint();
596
- const url = `${endpoint}/fingerprint/score`;
721
+ const url = `${this.getDefaultEndpoint()}/fingerprint/score`;
597
722
  const headers = {
598
723
  "Content-Type": "application/json",
599
724
  "X-SDK-Source": "react"
600
725
  // Identify SDK source for backend analytics
601
726
  };
727
+ const { origin, referrer } = this.getOriginHeaders();
728
+ if (origin) {
729
+ headers["X-Origin-URL"] = origin;
730
+ }
731
+ if (referrer) {
732
+ headers["X-Referrer-URL"] = referrer;
733
+ }
602
734
  const apiKey = this.config.apiKey;
603
735
  if (apiKey) {
604
736
  headers["x-keverd-key"] = apiKey;
@@ -608,10 +740,17 @@ var KeverdSDK = class {
608
740
  if (options?.tag === "sandbox") {
609
741
  headers["X-Sandbox"] = "true";
610
742
  }
743
+ const requestBody = {
744
+ ...request,
745
+ session: {
746
+ ...request.session || {},
747
+ sessionId: this.sessionId || request.session?.sessionId
748
+ }
749
+ };
611
750
  const response = await fetch(url, {
612
751
  method: "POST",
613
752
  headers,
614
- body: JSON.stringify(request)
753
+ body: JSON.stringify(requestBody)
615
754
  });
616
755
  if (!response.ok) {
617
756
  const errorText = await response.text().catch(() => "Unknown error");
@@ -649,7 +788,7 @@ var KeverdSDK = class {
649
788
  * Get default endpoint
650
789
  */
651
790
  getDefaultEndpoint() {
652
- return "https://app.keverd.com";
791
+ return "https://api.keverd.com";
653
792
  }
654
793
  /**
655
794
  * Generate a session ID
@@ -657,10 +796,204 @@ var KeverdSDK = class {
657
796
  generateSessionId() {
658
797
  return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
659
798
  }
799
+ /**
800
+ * Start a new session (called automatically on init, but can be called manually)
801
+ */
802
+ async startSession(userId, deviceHash, metadata) {
803
+ if (!this.isInitialized || !this.config) {
804
+ throw new Error("Keverd SDK not initialized. Call init() first.");
805
+ }
806
+ if (!this.sessionId) {
807
+ this.sessionId = this.generateSessionId();
808
+ }
809
+ try {
810
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/start`;
811
+ const headers = {
812
+ "Content-Type": "application/json"
813
+ };
814
+ const apiKey = this.config.apiKey;
815
+ if (apiKey) {
816
+ headers["x-keverd-key"] = apiKey;
817
+ headers["X-API-KEY"] = apiKey;
818
+ headers["Authorization"] = `Bearer ${apiKey}`;
819
+ }
820
+ const deviceInfo = this.deviceCollector.collect();
821
+ await fetch(url, {
822
+ method: "POST",
823
+ headers,
824
+ body: JSON.stringify({
825
+ session_id: this.sessionId,
826
+ user_id: userId || this.config.userId,
827
+ device_hash: deviceHash || deviceInfo.fingerprint,
828
+ session_metadata: metadata || {},
829
+ user_agent: navigator.userAgent,
830
+ browser: this._detectBrowser(),
831
+ os: this._detectOS(),
832
+ platform: "web",
833
+ sdk_type: "web",
834
+ sdk_source: "react"
835
+ })
836
+ });
837
+ if (this.config.debug) {
838
+ console.log(`[Keverd SDK] Session started: ${this.sessionId}`);
839
+ }
840
+ } catch (error) {
841
+ if (this.config.debug) {
842
+ console.warn("[Keverd SDK] Failed to start session on server:", error);
843
+ }
844
+ }
845
+ }
846
+ /**
847
+ * End the current session
848
+ */
849
+ async endSession() {
850
+ if (!this.isInitialized || !this.config || !this.sessionId) {
851
+ return;
852
+ }
853
+ try {
854
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/end`;
855
+ const headers = {
856
+ "Content-Type": "application/json"
857
+ };
858
+ const apiKey = this.config.apiKey;
859
+ if (apiKey) {
860
+ headers["x-keverd-key"] = apiKey;
861
+ headers["X-API-KEY"] = apiKey;
862
+ headers["Authorization"] = `Bearer ${apiKey}`;
863
+ }
864
+ await fetch(url, {
865
+ method: "POST",
866
+ headers
867
+ });
868
+ if (this.config.debug) {
869
+ console.log(`[Keverd SDK] Session ended: ${this.sessionId}`);
870
+ }
871
+ } catch (error) {
872
+ if (this.config.debug) {
873
+ console.warn("[Keverd SDK] Failed to end session on server:", error);
874
+ }
875
+ }
876
+ }
877
+ /**
878
+ * Pause the current session (e.g., when app goes to background)
879
+ */
880
+ async pauseSession() {
881
+ if (!this.isInitialized || !this.config || !this.sessionId) {
882
+ return;
883
+ }
884
+ try {
885
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/pause`;
886
+ const headers = {
887
+ "Content-Type": "application/json"
888
+ };
889
+ const apiKey = this.config.apiKey;
890
+ if (apiKey) {
891
+ headers["x-keverd-key"] = apiKey;
892
+ headers["X-API-KEY"] = apiKey;
893
+ headers["Authorization"] = `Bearer ${apiKey}`;
894
+ }
895
+ await fetch(url, {
896
+ method: "POST",
897
+ headers
898
+ });
899
+ if (this.config.debug) {
900
+ console.log(`[Keverd SDK] Session paused: ${this.sessionId}`);
901
+ }
902
+ } catch (error) {
903
+ if (this.config.debug) {
904
+ console.warn("[Keverd SDK] Failed to pause session on server:", error);
905
+ }
906
+ }
907
+ }
908
+ /**
909
+ * Resume a paused session (e.g., when app comes to foreground)
910
+ */
911
+ async resumeSession() {
912
+ if (!this.isInitialized || !this.config || !this.sessionId) {
913
+ return;
914
+ }
915
+ try {
916
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/resume`;
917
+ const headers = {
918
+ "Content-Type": "application/json"
919
+ };
920
+ const apiKey = this.config.apiKey;
921
+ if (apiKey) {
922
+ headers["x-keverd-key"] = apiKey;
923
+ headers["X-API-KEY"] = apiKey;
924
+ headers["Authorization"] = `Bearer ${apiKey}`;
925
+ }
926
+ await fetch(url, {
927
+ method: "POST",
928
+ headers
929
+ });
930
+ if (this.config.debug) {
931
+ console.log(`[Keverd SDK] Session resumed: ${this.sessionId}`);
932
+ }
933
+ } catch (error) {
934
+ if (this.config.debug) {
935
+ console.warn("[Keverd SDK] Failed to resume session on server:", error);
936
+ }
937
+ }
938
+ }
939
+ /**
940
+ * Get current session status
941
+ */
942
+ async getSessionStatus() {
943
+ if (!this.isInitialized || !this.config || !this.sessionId) {
944
+ return null;
945
+ }
946
+ try {
947
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/status`;
948
+ const headers = {};
949
+ const apiKey = this.config.apiKey;
950
+ if (apiKey) {
951
+ headers["x-keverd-key"] = apiKey;
952
+ headers["X-API-KEY"] = apiKey;
953
+ headers["Authorization"] = `Bearer ${apiKey}`;
954
+ }
955
+ const response = await fetch(url, {
956
+ method: "GET",
957
+ headers
958
+ });
959
+ if (!response.ok) {
960
+ return null;
961
+ }
962
+ return await response.json();
963
+ } catch (error) {
964
+ if (this.config.debug) {
965
+ console.warn("[Keverd SDK] Failed to get session status:", error);
966
+ }
967
+ return null;
968
+ }
969
+ }
970
+ /**
971
+ * Helper methods for browser/OS detection
972
+ */
973
+ _detectBrowser() {
974
+ const ua = navigator.userAgent;
975
+ if (ua.includes("Chrome") && !ua.includes("Edg")) return "Chrome";
976
+ if (ua.includes("Firefox")) return "Firefox";
977
+ if (ua.includes("Safari") && !ua.includes("Chrome")) return "Safari";
978
+ if (ua.includes("Edg")) return "Edge";
979
+ return "Unknown";
980
+ }
981
+ _detectOS() {
982
+ const ua = navigator.userAgent;
983
+ if (ua.includes("Windows")) return "Windows";
984
+ if (ua.includes("Mac")) return "macOS";
985
+ if (ua.includes("Linux")) return "Linux";
986
+ if (ua.includes("Android")) return "Android";
987
+ if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
988
+ return "Unknown";
989
+ }
660
990
  /**
661
991
  * Destroy the SDK instance
662
992
  */
663
- destroy() {
993
+ async destroy() {
994
+ if (this.sessionId) {
995
+ await this.endSession();
996
+ }
664
997
  const wasDebug = this.config?.debug;
665
998
  this.behavioralCollector.stop();
666
999
  this.isInitialized = false;
@@ -692,14 +1025,14 @@ function KeverdProvider({ loadOptions, children }) {
692
1025
  if (!sdk.isReady()) {
693
1026
  sdk.init({
694
1027
  apiKey: loadOptions.apiKey,
695
- endpoint: loadOptions.endpoint,
696
1028
  debug: loadOptions.debug || false
697
1029
  });
698
1030
  setIsReady(true);
699
1031
  }
700
1032
  return () => {
701
1033
  if (sdk.isReady()) {
702
- sdk.destroy();
1034
+ sdk.destroy().catch(() => {
1035
+ });
703
1036
  setIsReady(false);
704
1037
  }
705
1038
  };