@keverdjs/fraud-sdk-react 1.0.0 → 2.0.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.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)
@@ -592,8 +707,7 @@ var KeverdSDK = class {
592
707
  if (!this.config) {
593
708
  throw new Error("SDK not initialized");
594
709
  }
595
- const endpoint = this.config.endpoint || this.getDefaultEndpoint();
596
- const url = `${endpoint}/fingerprint/score`;
710
+ const url = `${this.getDefaultEndpoint()}/fingerprint/score`;
597
711
  const headers = {
598
712
  "Content-Type": "application/json",
599
713
  "X-SDK-Source": "react"
@@ -608,10 +722,17 @@ var KeverdSDK = class {
608
722
  if (options?.tag === "sandbox") {
609
723
  headers["X-Sandbox"] = "true";
610
724
  }
725
+ const requestBody = {
726
+ ...request,
727
+ session: {
728
+ ...request.session || {},
729
+ sessionId: this.sessionId || request.session?.sessionId
730
+ }
731
+ };
611
732
  const response = await fetch(url, {
612
733
  method: "POST",
613
734
  headers,
614
- body: JSON.stringify(request)
735
+ body: JSON.stringify(requestBody)
615
736
  });
616
737
  if (!response.ok) {
617
738
  const errorText = await response.text().catch(() => "Unknown error");
@@ -649,7 +770,7 @@ var KeverdSDK = class {
649
770
  * Get default endpoint
650
771
  */
651
772
  getDefaultEndpoint() {
652
- return "https://app.keverd.com";
773
+ return "https://api.keverd.com";
653
774
  }
654
775
  /**
655
776
  * Generate a session ID
@@ -657,10 +778,204 @@ var KeverdSDK = class {
657
778
  generateSessionId() {
658
779
  return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
659
780
  }
781
+ /**
782
+ * Start a new session (called automatically on init, but can be called manually)
783
+ */
784
+ async startSession(userId, deviceHash, metadata) {
785
+ if (!this.isInitialized || !this.config) {
786
+ throw new Error("Keverd SDK not initialized. Call init() first.");
787
+ }
788
+ if (!this.sessionId) {
789
+ this.sessionId = this.generateSessionId();
790
+ }
791
+ try {
792
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/start`;
793
+ const headers = {
794
+ "Content-Type": "application/json"
795
+ };
796
+ const apiKey = this.config.apiKey;
797
+ if (apiKey) {
798
+ headers["x-keverd-key"] = apiKey;
799
+ headers["X-API-KEY"] = apiKey;
800
+ headers["Authorization"] = `Bearer ${apiKey}`;
801
+ }
802
+ const deviceInfo = this.deviceCollector.collect();
803
+ await fetch(url, {
804
+ method: "POST",
805
+ headers,
806
+ body: JSON.stringify({
807
+ session_id: this.sessionId,
808
+ user_id: userId || this.config.userId,
809
+ device_hash: deviceHash || deviceInfo.fingerprint,
810
+ session_metadata: metadata || {},
811
+ user_agent: navigator.userAgent,
812
+ browser: this._detectBrowser(),
813
+ os: this._detectOS(),
814
+ platform: "web",
815
+ sdk_type: "web",
816
+ sdk_source: "react"
817
+ })
818
+ });
819
+ if (this.config.debug) {
820
+ console.log(`[Keverd SDK] Session started: ${this.sessionId}`);
821
+ }
822
+ } catch (error) {
823
+ if (this.config.debug) {
824
+ console.warn("[Keverd SDK] Failed to start session on server:", error);
825
+ }
826
+ }
827
+ }
828
+ /**
829
+ * End the current session
830
+ */
831
+ async endSession() {
832
+ if (!this.isInitialized || !this.config || !this.sessionId) {
833
+ return;
834
+ }
835
+ try {
836
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/end`;
837
+ const headers = {
838
+ "Content-Type": "application/json"
839
+ };
840
+ const apiKey = this.config.apiKey;
841
+ if (apiKey) {
842
+ headers["x-keverd-key"] = apiKey;
843
+ headers["X-API-KEY"] = apiKey;
844
+ headers["Authorization"] = `Bearer ${apiKey}`;
845
+ }
846
+ await fetch(url, {
847
+ method: "POST",
848
+ headers
849
+ });
850
+ if (this.config.debug) {
851
+ console.log(`[Keverd SDK] Session ended: ${this.sessionId}`);
852
+ }
853
+ } catch (error) {
854
+ if (this.config.debug) {
855
+ console.warn("[Keverd SDK] Failed to end session on server:", error);
856
+ }
857
+ }
858
+ }
859
+ /**
860
+ * Pause the current session (e.g., when app goes to background)
861
+ */
862
+ async pauseSession() {
863
+ if (!this.isInitialized || !this.config || !this.sessionId) {
864
+ return;
865
+ }
866
+ try {
867
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/pause`;
868
+ const headers = {
869
+ "Content-Type": "application/json"
870
+ };
871
+ const apiKey = this.config.apiKey;
872
+ if (apiKey) {
873
+ headers["x-keverd-key"] = apiKey;
874
+ headers["X-API-KEY"] = apiKey;
875
+ headers["Authorization"] = `Bearer ${apiKey}`;
876
+ }
877
+ await fetch(url, {
878
+ method: "POST",
879
+ headers
880
+ });
881
+ if (this.config.debug) {
882
+ console.log(`[Keverd SDK] Session paused: ${this.sessionId}`);
883
+ }
884
+ } catch (error) {
885
+ if (this.config.debug) {
886
+ console.warn("[Keverd SDK] Failed to pause session on server:", error);
887
+ }
888
+ }
889
+ }
890
+ /**
891
+ * Resume a paused session (e.g., when app comes to foreground)
892
+ */
893
+ async resumeSession() {
894
+ if (!this.isInitialized || !this.config || !this.sessionId) {
895
+ return;
896
+ }
897
+ try {
898
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/resume`;
899
+ const headers = {
900
+ "Content-Type": "application/json"
901
+ };
902
+ const apiKey = this.config.apiKey;
903
+ if (apiKey) {
904
+ headers["x-keverd-key"] = apiKey;
905
+ headers["X-API-KEY"] = apiKey;
906
+ headers["Authorization"] = `Bearer ${apiKey}`;
907
+ }
908
+ await fetch(url, {
909
+ method: "POST",
910
+ headers
911
+ });
912
+ if (this.config.debug) {
913
+ console.log(`[Keverd SDK] Session resumed: ${this.sessionId}`);
914
+ }
915
+ } catch (error) {
916
+ if (this.config.debug) {
917
+ console.warn("[Keverd SDK] Failed to resume session on server:", error);
918
+ }
919
+ }
920
+ }
921
+ /**
922
+ * Get current session status
923
+ */
924
+ async getSessionStatus() {
925
+ if (!this.isInitialized || !this.config || !this.sessionId) {
926
+ return null;
927
+ }
928
+ try {
929
+ const url = `${this.getDefaultEndpoint()}/dashboard/sessions/${this.sessionId}/status`;
930
+ const headers = {};
931
+ const apiKey = this.config.apiKey;
932
+ if (apiKey) {
933
+ headers["x-keverd-key"] = apiKey;
934
+ headers["X-API-KEY"] = apiKey;
935
+ headers["Authorization"] = `Bearer ${apiKey}`;
936
+ }
937
+ const response = await fetch(url, {
938
+ method: "GET",
939
+ headers
940
+ });
941
+ if (!response.ok) {
942
+ return null;
943
+ }
944
+ return await response.json();
945
+ } catch (error) {
946
+ if (this.config.debug) {
947
+ console.warn("[Keverd SDK] Failed to get session status:", error);
948
+ }
949
+ return null;
950
+ }
951
+ }
952
+ /**
953
+ * Helper methods for browser/OS detection
954
+ */
955
+ _detectBrowser() {
956
+ const ua = navigator.userAgent;
957
+ if (ua.includes("Chrome") && !ua.includes("Edg")) return "Chrome";
958
+ if (ua.includes("Firefox")) return "Firefox";
959
+ if (ua.includes("Safari") && !ua.includes("Chrome")) return "Safari";
960
+ if (ua.includes("Edg")) return "Edge";
961
+ return "Unknown";
962
+ }
963
+ _detectOS() {
964
+ const ua = navigator.userAgent;
965
+ if (ua.includes("Windows")) return "Windows";
966
+ if (ua.includes("Mac")) return "macOS";
967
+ if (ua.includes("Linux")) return "Linux";
968
+ if (ua.includes("Android")) return "Android";
969
+ if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
970
+ return "Unknown";
971
+ }
660
972
  /**
661
973
  * Destroy the SDK instance
662
974
  */
663
- destroy() {
975
+ async destroy() {
976
+ if (this.sessionId) {
977
+ await this.endSession();
978
+ }
664
979
  const wasDebug = this.config?.debug;
665
980
  this.behavioralCollector.stop();
666
981
  this.isInitialized = false;
@@ -692,14 +1007,14 @@ function KeverdProvider({ loadOptions, children }) {
692
1007
  if (!sdk.isReady()) {
693
1008
  sdk.init({
694
1009
  apiKey: loadOptions.apiKey,
695
- endpoint: loadOptions.endpoint,
696
1010
  debug: loadOptions.debug || false
697
1011
  });
698
1012
  setIsReady(true);
699
1013
  }
700
1014
  return () => {
701
1015
  if (sdk.isReady()) {
702
- sdk.destroy();
1016
+ sdk.destroy().catch(() => {
1017
+ });
703
1018
  setIsReady(false);
704
1019
  }
705
1020
  };