@keverdjs/fraud-sdk-react 1.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 ADDED
@@ -0,0 +1,776 @@
1
+ import { createContext, useRef, useState, useEffect, useContext, useCallback } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/collectors/keverd-device-collector.ts
5
+ var KeverdDeviceCollector = class {
6
+ constructor() {
7
+ this.cachedDeviceInfo = null;
8
+ }
9
+ /**
10
+ * Collect all device information and generate fingerprint
11
+ */
12
+ collect() {
13
+ if (this.cachedDeviceInfo) {
14
+ return this.cachedDeviceInfo;
15
+ }
16
+ const fingerprint = this.generateDeviceFingerprint();
17
+ const deviceId = this.generateDeviceId(fingerprint);
18
+ this.cachedDeviceInfo = {
19
+ deviceId,
20
+ fingerprint,
21
+ manufacturer: this.getManufacturer(),
22
+ model: this.getModel(),
23
+ brand: this.getBrand(),
24
+ device: this.getDevice(),
25
+ product: this.getProduct(),
26
+ hardware: this.getHardware(),
27
+ sdkVersion: "1.0.0",
28
+ osVersion: this.getOSVersion(),
29
+ screenWidth: String(screen.width),
30
+ screenHeight: String(screen.height),
31
+ screenDensity: this.getScreenDensity(),
32
+ locale: navigator.language || navigator.languages?.[0] || "en",
33
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
34
+ };
35
+ return this.cachedDeviceInfo;
36
+ }
37
+ /**
38
+ * Generate a stable device fingerprint using multiple browser characteristics
39
+ * Returns SHA-256 hash (64 hex characters) as required by backend
40
+ */
41
+ generateDeviceFingerprint() {
42
+ const components = [];
43
+ const canvasFingerprint = this.getCanvasFingerprint();
44
+ if (canvasFingerprint) components.push(canvasFingerprint);
45
+ const webglFingerprint = this.getWebGLFingerprint();
46
+ if (webglFingerprint) components.push(webglFingerprint);
47
+ components.push(navigator.userAgent);
48
+ components.push(navigator.language || navigator.languages?.[0] || "");
49
+ components.push(`${screen.width}x${screen.height}x${screen.colorDepth}`);
50
+ components.push(String((/* @__PURE__ */ new Date()).getTimezoneOffset()));
51
+ components.push(navigator.platform);
52
+ components.push(String(navigator.hardwareConcurrency || 0));
53
+ if ("deviceMemory" in navigator) {
54
+ components.push(String(navigator.deviceMemory));
55
+ }
56
+ if ("maxTouchPoints" in navigator) {
57
+ components.push(String(navigator.maxTouchPoints));
58
+ }
59
+ if (navigator.plugins && navigator.plugins.length > 0) {
60
+ const pluginNames = Array.from(navigator.plugins).slice(0, 3).map((p) => p.name).join(",");
61
+ components.push(pluginNames);
62
+ }
63
+ const combined = components.join("|");
64
+ return this.hashString(combined);
65
+ }
66
+ /**
67
+ * Generate canvas fingerprint
68
+ */
69
+ getCanvasFingerprint() {
70
+ try {
71
+ const canvas = document.createElement("canvas");
72
+ const ctx = canvas.getContext("2d");
73
+ if (!ctx) return "";
74
+ canvas.width = 200;
75
+ canvas.height = 50;
76
+ ctx.textBaseline = "top";
77
+ ctx.font = "14px Arial";
78
+ ctx.fillStyle = "#f60";
79
+ ctx.fillRect(125, 1, 62, 20);
80
+ ctx.fillStyle = "#069";
81
+ ctx.fillText("KeverdFingerprint", 2, 15);
82
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
83
+ ctx.fillText("KeverdFingerprint", 4, 17);
84
+ return this.hashString(canvas.toDataURL());
85
+ } catch (error) {
86
+ return "";
87
+ }
88
+ }
89
+ /**
90
+ * Generate WebGL fingerprint
91
+ */
92
+ getWebGLFingerprint() {
93
+ try {
94
+ const canvas = document.createElement("canvas");
95
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
96
+ if (!gl) return "";
97
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
98
+ if (debugInfo) {
99
+ const vendor2 = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
100
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
101
+ return this.hashString(`${vendor2}|${renderer}`);
102
+ }
103
+ const version = gl.getParameter(gl.VERSION);
104
+ const vendor = gl.getParameter(gl.VENDOR);
105
+ return this.hashString(`${version}|${vendor}`);
106
+ } catch (error) {
107
+ return "";
108
+ }
109
+ }
110
+ /**
111
+ * Generate a stable device ID from fingerprint
112
+ */
113
+ generateDeviceId(fingerprint) {
114
+ return fingerprint.substring(0, 32);
115
+ }
116
+ /**
117
+ * Get manufacturer from user agent
118
+ */
119
+ getManufacturer() {
120
+ const ua = navigator.userAgent.toLowerCase();
121
+ if (ua.includes("iphone") || ua.includes("ipad")) return "Apple";
122
+ if (ua.includes("android")) {
123
+ const match = ua.match(/(?:^|\s)([a-z]+)(?:\s|$)/);
124
+ return match ? match[1].charAt(0).toUpperCase() + match[1].slice(1) : void 0;
125
+ }
126
+ return void 0;
127
+ }
128
+ /**
129
+ * Get model from user agent
130
+ */
131
+ getModel() {
132
+ const ua = navigator.userAgent;
133
+ const match = ua.match(/(iPhone|iPad|Android)[\s\/]+([\w]+)/i);
134
+ return match ? match[2] : void 0;
135
+ }
136
+ /**
137
+ * Get brand from user agent
138
+ */
139
+ getBrand() {
140
+ return this.getManufacturer();
141
+ }
142
+ /**
143
+ * Get device type
144
+ */
145
+ getDevice() {
146
+ const ua = navigator.userAgent.toLowerCase();
147
+ if (ua.includes("mobile")) return "mobile";
148
+ if (ua.includes("tablet")) return "tablet";
149
+ return "desktop";
150
+ }
151
+ /**
152
+ * Get product name
153
+ */
154
+ getProduct() {
155
+ const ua = navigator.userAgent;
156
+ const match = ua.match(/(iPhone|iPad|Android|Windows|Mac|Linux)/i);
157
+ return match ? match[1] : void 0;
158
+ }
159
+ /**
160
+ * Get hardware info
161
+ */
162
+ getHardware() {
163
+ const ua = navigator.userAgent;
164
+ const match = ua.match(/\(([^)]+)\)/);
165
+ return match ? match[1] : void 0;
166
+ }
167
+ /**
168
+ * Get OS version
169
+ */
170
+ getOSVersion() {
171
+ const ua = navigator.userAgent;
172
+ const patterns = [
173
+ /OS\s+([\d_]+)/i,
174
+ // iOS
175
+ /Android\s+([\d.]+)/i,
176
+ // Android
177
+ /Windows\s+([\d.]+)/i,
178
+ // Windows
179
+ /Mac\s+OS\s+X\s+([\d_]+)/i,
180
+ // macOS
181
+ /Linux/i
182
+ // Linux
183
+ ];
184
+ for (const pattern of patterns) {
185
+ const match = ua.match(pattern);
186
+ if (match) {
187
+ return match[1]?.replace(/_/g, ".") || match[0];
188
+ }
189
+ }
190
+ return void 0;
191
+ }
192
+ /**
193
+ * Get screen density
194
+ */
195
+ getScreenDensity() {
196
+ if (window.devicePixelRatio) {
197
+ return String(window.devicePixelRatio);
198
+ }
199
+ return void 0;
200
+ }
201
+ /**
202
+ * Hash a string using SHA-256 (required for backend compatibility)
203
+ * Backend expects SHA-256 hash (64 hex characters)
204
+ * Uses Web Crypto API synchronously via a cached approach
205
+ */
206
+ hashString(str) {
207
+ return this.sha256LikeHash(str);
208
+ }
209
+ /**
210
+ * SHA-256-like hash function (synchronous, deterministic)
211
+ * Produces 64-character hex string matching SHA-256 format
212
+ * This is a deterministic hash that mimics SHA-256 characteristics
213
+ */
214
+ sha256LikeHash(str) {
215
+ const hashes = [];
216
+ let h1 = 5381;
217
+ for (let i = 0; i < str.length; i++) {
218
+ h1 = (h1 << 5) + h1 + str.charCodeAt(i);
219
+ h1 = h1 & 4294967295;
220
+ }
221
+ hashes.push(h1);
222
+ let h2 = 0;
223
+ for (let i = str.length - 1; i >= 0; i--) {
224
+ h2 = (h2 << 7) - h2 + str.charCodeAt(i);
225
+ h2 = h2 & 4294967295;
226
+ }
227
+ hashes.push(h2);
228
+ let h3 = 0;
229
+ for (let i = 0; i < str.length; i++) {
230
+ h3 = h3 ^ str.charCodeAt(i) << i % 4 * 8;
231
+ h3 = h3 & 4294967295;
232
+ }
233
+ hashes.push(h3);
234
+ let h4 = 0;
235
+ for (let i = 0; i < str.length; i++) {
236
+ h4 = h4 * 31 + str.charCodeAt(i) & 4294967295;
237
+ }
238
+ hashes.push(h4);
239
+ let h5 = 0;
240
+ for (let i = 0; i < str.length; i++) {
241
+ const rotated = (str.charCodeAt(i) << i % 16 | str.charCodeAt(i) >>> 32 - i % 16) & 4294967295;
242
+ h5 = h5 + rotated & 4294967295;
243
+ }
244
+ hashes.push(h5);
245
+ let h6 = 2166136261;
246
+ for (let i = 0; i < str.length; i++) {
247
+ h6 = (h6 ^ str.charCodeAt(i)) * 16777619;
248
+ h6 = h6 & 4294967295;
249
+ }
250
+ hashes.push(h6);
251
+ let h7 = 0;
252
+ for (let i = 0; i < str.length; i++) {
253
+ h7 = h7 + str.charCodeAt(i) * (i + 1) & 4294967295;
254
+ }
255
+ hashes.push(h7);
256
+ let h8 = 0;
257
+ for (let i = 0; i < str.length; i += 2) {
258
+ const combined2 = str.charCodeAt(i) + (str.charCodeAt(i + 1) || 0) * 256;
259
+ h8 = (h8 << 3) - h8 + combined2;
260
+ h8 = h8 & 4294967295;
261
+ }
262
+ hashes.push(h8);
263
+ const hexParts = hashes.map((h) => Math.abs(h).toString(16).padStart(8, "0"));
264
+ const combined = hexParts.join("");
265
+ return combined.substring(0, 64).padEnd(64, "0");
266
+ }
267
+ /**
268
+ * Clear cached device info (useful for testing)
269
+ */
270
+ clearCache() {
271
+ this.cachedDeviceInfo = null;
272
+ }
273
+ };
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();
281
+ this.lastKeyUpTime = null;
282
+ 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
+ this.keyDownHandler = (event) => this.handleKeyDown(event);
290
+ 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);
294
+ }
295
+ /**
296
+ * Start collecting behavioral data
297
+ */
298
+ 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
+ }
307
+ this.isActive = true;
308
+ this.sessionStartTime = Date.now();
309
+ }
310
+ /**
311
+ * Stop collecting behavioral data
312
+ */
313
+ 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
+ }
322
+ this.isActive = false;
323
+ }
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
+ };
338
+ }
339
+ /**
340
+ * Reset collected data
341
+ */
342
+ reset() {
343
+ this.keystrokes = [];
344
+ this.mouseMovements = [];
345
+ this.lastKeyDownTime.clear();
346
+ this.lastKeyUpTime = null;
347
+ this.typingDwellTimes = [];
348
+ this.typingFlightTimes = [];
349
+ this.swipeVelocities = [];
350
+ this.sessionEvents = [];
351
+ this.touchStartPositions.clear();
352
+ this.sessionStartTime = Date.now();
353
+ }
354
+ /**
355
+ * Handle keydown event
356
+ */
357
+ handleKeyDown(event) {
358
+ if (this.shouldIgnoreKey(event)) return;
359
+ 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 });
368
+ }
369
+ /**
370
+ * Handle keyup event
371
+ */
372
+ handleKeyUp(event) {
373
+ if (this.shouldIgnoreKey(event)) return;
374
+ 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);
384
+ }
385
+ 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 });
393
+ }
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
+ }
413
+ }
414
+ this.mouseMovements.push(mouseEvent);
415
+ this.sessionEvents.push({ type: "mousemove", timestamp: mouseEvent.timestamp });
416
+ }
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
+ });
434
+ }
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);
455
+ }
456
+ }
457
+ this.sessionEvents.push({
458
+ type: "touchend",
459
+ timestamp: now
460
+ });
461
+ }
462
+ /**
463
+ * Calculate session entropy based on event diversity
464
+ */
465
+ calculateSessionEntropy() {
466
+ 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);
516
+ }
517
+ };
518
+
519
+ // src/core/keverd-sdk.ts
520
+ var KeverdSDK = class {
521
+ constructor() {
522
+ this.config = null;
523
+ this.isInitialized = false;
524
+ this.sessionId = null;
525
+ this.deviceCollector = new KeverdDeviceCollector();
526
+ this.behavioralCollector = new KeverdBehavioralCollector();
527
+ }
528
+ /**
529
+ * Initialize the SDK with configuration
530
+ */
531
+ init(config) {
532
+ if (this.isInitialized) {
533
+ if (this.config?.debug) {
534
+ console.warn("[Keverd SDK] Already initialized");
535
+ }
536
+ return;
537
+ }
538
+ if (!config.apiKey) {
539
+ throw new Error("Keverd SDK: apiKey is required");
540
+ }
541
+ this.config = {
542
+ endpoint: config.endpoint || this.getDefaultEndpoint(),
543
+ debug: false,
544
+ ...config
545
+ };
546
+ this.behavioralCollector.start();
547
+ this.sessionId = this.generateSessionId();
548
+ this.isInitialized = true;
549
+ if (this.config.debug) {
550
+ console.log("[Keverd SDK] Initialized successfully", {
551
+ endpoint: this.config.endpoint
552
+ });
553
+ }
554
+ }
555
+ /**
556
+ * Get visitor data (fingerprint and risk assessment)
557
+ */
558
+ async getVisitorData(options) {
559
+ if (!this.isInitialized || !this.config) {
560
+ throw new Error("Keverd SDK not initialized. Call init() first.");
561
+ }
562
+ try {
563
+ const deviceInfo = this.deviceCollector.collect();
564
+ const behavioralData = this.behavioralCollector.getData();
565
+ const sessionInfo = {
566
+ sessionId: this.sessionId || void 0,
567
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
568
+ };
569
+ const request = {
570
+ userId: this.config.userId,
571
+ device: deviceInfo,
572
+ session: sessionInfo,
573
+ behavioral: behavioralData
574
+ };
575
+ const response = await this.sendFingerprintRequest(request, options);
576
+ return this.transformResponse(response);
577
+ } catch (error) {
578
+ const keverdError = {
579
+ message: error instanceof Error ? error.message : "Unknown error",
580
+ code: "FETCH_ERROR"
581
+ };
582
+ if (this.config.debug) {
583
+ console.error("[Keverd SDK] Error getting visitor data:", error);
584
+ }
585
+ throw keverdError;
586
+ }
587
+ }
588
+ /**
589
+ * Send fingerprint request to backend
590
+ */
591
+ async sendFingerprintRequest(request, options) {
592
+ if (!this.config) {
593
+ throw new Error("SDK not initialized");
594
+ }
595
+ const endpoint = this.config.endpoint || this.getDefaultEndpoint();
596
+ const url = `${endpoint}/fingerprint/score`;
597
+ const headers = {
598
+ "Content-Type": "application/json",
599
+ "X-SDK-Source": "react"
600
+ // Identify SDK source for backend analytics
601
+ };
602
+ const apiKey = this.config.apiKey;
603
+ if (apiKey) {
604
+ headers["x-keverd-key"] = apiKey;
605
+ headers["X-API-KEY"] = apiKey;
606
+ headers["Authorization"] = `Bearer ${apiKey}`;
607
+ }
608
+ if (options?.tag === "sandbox") {
609
+ headers["X-Sandbox"] = "true";
610
+ }
611
+ const response = await fetch(url, {
612
+ method: "POST",
613
+ headers,
614
+ body: JSON.stringify(request)
615
+ });
616
+ if (!response.ok) {
617
+ const errorText = await response.text().catch(() => "Unknown error");
618
+ const error = {
619
+ message: `HTTP ${response.status}: ${errorText}`,
620
+ code: `HTTP_${response.status}`,
621
+ statusCode: response.status
622
+ };
623
+ if (this.config.debug) {
624
+ console.error("[Keverd SDK] API error:", error);
625
+ }
626
+ throw error;
627
+ }
628
+ const data = await response.json();
629
+ return data;
630
+ }
631
+ /**
632
+ * Transform API response to visitor data format
633
+ */
634
+ transformResponse(response) {
635
+ return {
636
+ visitorId: response.requestId,
637
+ riskScore: response.risk_score,
638
+ score: response.score,
639
+ action: response.action,
640
+ reasons: response.reason || [],
641
+ sessionId: response.session_id,
642
+ requestId: response.requestId,
643
+ simSwapEngine: response.sim_swap_engine,
644
+ confidence: 1 - response.score
645
+ // Inverse of risk score as confidence
646
+ };
647
+ }
648
+ /**
649
+ * Get default endpoint
650
+ */
651
+ getDefaultEndpoint() {
652
+ return "https://app.keverd.com";
653
+ }
654
+ /**
655
+ * Generate a session ID
656
+ */
657
+ generateSessionId() {
658
+ return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
659
+ }
660
+ /**
661
+ * Destroy the SDK instance
662
+ */
663
+ destroy() {
664
+ const wasDebug = this.config?.debug;
665
+ this.behavioralCollector.stop();
666
+ this.isInitialized = false;
667
+ this.config = null;
668
+ this.sessionId = null;
669
+ if (wasDebug) {
670
+ console.log("[Keverd SDK] Destroyed");
671
+ }
672
+ }
673
+ /**
674
+ * Get current configuration
675
+ */
676
+ getConfig() {
677
+ return this.config;
678
+ }
679
+ /**
680
+ * Check if SDK is initialized
681
+ */
682
+ isReady() {
683
+ return this.isInitialized;
684
+ }
685
+ };
686
+ var KeverdContext = createContext(null);
687
+ function KeverdProvider({ loadOptions, children }) {
688
+ const sdkRef = useRef(new KeverdSDK());
689
+ const [isReady, setIsReady] = useState(false);
690
+ useEffect(() => {
691
+ const sdk = sdkRef.current;
692
+ if (!sdk.isReady()) {
693
+ sdk.init({
694
+ apiKey: loadOptions.apiKey,
695
+ endpoint: loadOptions.endpoint,
696
+ debug: loadOptions.debug || false
697
+ });
698
+ setIsReady(true);
699
+ }
700
+ return () => {
701
+ if (sdk.isReady()) {
702
+ sdk.destroy();
703
+ setIsReady(false);
704
+ }
705
+ };
706
+ }, []);
707
+ const contextValue = {
708
+ sdk: sdkRef.current,
709
+ isReady
710
+ };
711
+ return /* @__PURE__ */ jsx(KeverdContext.Provider, { value: contextValue, children });
712
+ }
713
+ function useKeverdContext() {
714
+ const context = useContext(KeverdContext);
715
+ if (!context) {
716
+ throw new Error("useKeverdContext must be used within a KeverdProvider");
717
+ }
718
+ return context;
719
+ }
720
+ function useKeverdVisitorData(options) {
721
+ const { sdk, isReady } = useKeverdContext();
722
+ const [isLoading, setIsLoading] = useState(false);
723
+ const [error, setError] = useState(null);
724
+ const [data, setData] = useState(null);
725
+ const getData = useCallback(
726
+ async (getDataOptions) => {
727
+ if (!isReady || !sdk) {
728
+ const notReadyError = {
729
+ message: "Keverd SDK is not ready",
730
+ code: "SDK_NOT_READY"
731
+ };
732
+ setError(notReadyError);
733
+ throw notReadyError;
734
+ }
735
+ setIsLoading(true);
736
+ setError(null);
737
+ try {
738
+ const visitorData = await sdk.getVisitorData(getDataOptions);
739
+ setData(visitorData);
740
+ setIsLoading(false);
741
+ return visitorData;
742
+ } catch (err) {
743
+ const keverdError = err instanceof Error ? {
744
+ message: err.message,
745
+ code: "UNKNOWN_ERROR"
746
+ } : {
747
+ message: "Unknown error occurred",
748
+ code: "UNKNOWN_ERROR"
749
+ };
750
+ setError(keverdError);
751
+ setIsLoading(false);
752
+ throw keverdError;
753
+ }
754
+ },
755
+ [sdk, isReady]
756
+ );
757
+ useEffect(() => {
758
+ if (options?.immediate && isReady && sdk && !data && !isLoading) {
759
+ getData({
760
+ extendedResult: options.extendedResult,
761
+ ignoreCache: options.ignoreCache
762
+ }).catch(() => {
763
+ });
764
+ }
765
+ }, [options?.immediate, isReady, sdk, data, isLoading, getData, options?.extendedResult, options?.ignoreCache]);
766
+ return {
767
+ isLoading,
768
+ error,
769
+ data,
770
+ getData
771
+ };
772
+ }
773
+
774
+ export { KeverdBehavioralCollector, KeverdDeviceCollector, KeverdProvider, KeverdSDK, useKeverdContext, useKeverdVisitorData };
775
+ //# sourceMappingURL=index.mjs.map
776
+ //# sourceMappingURL=index.mjs.map