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