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