@keverdjs/fraud-sdk-angular 1.0.0 → 1.1.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
@@ -1,5 +1,6 @@
1
- import { Injectable, NgModule } from '@angular/core';
2
- import { from } from 'rxjs';
1
+ import { Injectable, Optional, Inject, NgModule } from '@angular/core';
2
+ import { throwError, from } from 'rxjs';
3
+ import { map, catchError } from 'rxjs/operators';
3
4
  import { CommonModule } from '@angular/common';
4
5
 
5
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -10,29 +11,3285 @@ var __decorateClass = (decorators, target, key, kind) => {
10
11
  result = (decorator(result)) || result;
11
12
  return result;
12
13
  };
13
- var KeverdService = class {
14
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
15
+
16
+ // ../fraud-sdk/src/collectors/device.ts
17
+ var DeviceCollector = class {
18
+ constructor() {
19
+ this.cachedDeviceInfo = null;
20
+ }
21
+ /**
22
+ * Collect all device information and generate fingerprint
23
+ */
24
+ collect() {
25
+ if (this.cachedDeviceInfo) {
26
+ return this.cachedDeviceInfo;
27
+ }
28
+ const fingerprint = this.generateDeviceFingerprint();
29
+ const deviceId = this.generateDeviceId(fingerprint);
30
+ this.cachedDeviceInfo = {
31
+ deviceId,
32
+ fingerprint,
33
+ manufacturer: this.getManufacturer(),
34
+ model: this.getModel(),
35
+ brand: this.getBrand(),
36
+ device: this.getDevice(),
37
+ product: this.getProduct(),
38
+ hardware: this.getHardware(),
39
+ sdkVersion: "1.0.0",
40
+ osVersion: this.getOSVersion(),
41
+ screenWidth: String(screen.width),
42
+ screenHeight: String(screen.height),
43
+ screenDensity: this.getScreenDensity(),
44
+ locale: navigator.language || navigator.languages?.[0] || "en",
45
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
46
+ };
47
+ return this.cachedDeviceInfo;
48
+ }
49
+ /**
50
+ * Generate a stable device fingerprint using multiple browser characteristics
51
+ * Returns SHA-256 hash (64 hex characters) as required by backend
52
+ */
53
+ generateDeviceFingerprint() {
54
+ const components = [];
55
+ const canvasFingerprint = this.getCanvasFingerprint();
56
+ if (canvasFingerprint) components.push(canvasFingerprint);
57
+ const webglFingerprint = this.getWebGLFingerprint();
58
+ if (webglFingerprint) components.push(webglFingerprint);
59
+ components.push(navigator.userAgent);
60
+ components.push(navigator.language || navigator.languages?.[0] || "");
61
+ components.push(`${screen.width}x${screen.height}x${screen.colorDepth}`);
62
+ components.push(String((/* @__PURE__ */ new Date()).getTimezoneOffset()));
63
+ components.push(navigator.platform);
64
+ components.push(String(navigator.hardwareConcurrency || 0));
65
+ if ("deviceMemory" in navigator) {
66
+ components.push(String(navigator.deviceMemory));
67
+ }
68
+ if ("maxTouchPoints" in navigator) {
69
+ components.push(String(navigator.maxTouchPoints));
70
+ }
71
+ if (navigator.plugins && navigator.plugins.length > 0) {
72
+ const pluginNames = Array.from(navigator.plugins).slice(0, 3).map((p) => p.name).join(",");
73
+ components.push(pluginNames);
74
+ }
75
+ const combined = components.join("|");
76
+ return this.hashString(combined);
77
+ }
78
+ /**
79
+ * Generate canvas fingerprint
80
+ */
81
+ getCanvasFingerprint() {
82
+ try {
83
+ const canvas = document.createElement("canvas");
84
+ const ctx = canvas.getContext("2d");
85
+ if (!ctx) return "";
86
+ canvas.width = 200;
87
+ canvas.height = 50;
88
+ ctx.textBaseline = "top";
89
+ ctx.font = "14px Arial";
90
+ ctx.fillStyle = "#f60";
91
+ ctx.fillRect(125, 1, 62, 20);
92
+ ctx.fillStyle = "#069";
93
+ ctx.fillText("KeverdFingerprint", 2, 15);
94
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
95
+ ctx.fillText("KeverdFingerprint", 4, 17);
96
+ return this.hashString(canvas.toDataURL());
97
+ } catch (error) {
98
+ return "";
99
+ }
100
+ }
101
+ /**
102
+ * Generate WebGL fingerprint
103
+ */
104
+ getWebGLFingerprint() {
105
+ try {
106
+ const canvas = document.createElement("canvas");
107
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
108
+ if (!gl) return "";
109
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
110
+ if (debugInfo) {
111
+ const vendor2 = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
112
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
113
+ return this.hashString(`${vendor2}|${renderer}`);
114
+ }
115
+ const version = gl.getParameter(gl.VERSION);
116
+ const vendor = gl.getParameter(gl.VENDOR);
117
+ return this.hashString(`${version}|${vendor}`);
118
+ } catch (error) {
119
+ return "";
120
+ }
121
+ }
122
+ /**
123
+ * Generate a stable device ID from fingerprint
124
+ */
125
+ generateDeviceId(fingerprint) {
126
+ return fingerprint.substring(0, 32);
127
+ }
128
+ /**
129
+ * Get manufacturer from user agent
130
+ */
131
+ getManufacturer() {
132
+ const ua = navigator.userAgent.toLowerCase();
133
+ if (ua.includes("iphone") || ua.includes("ipad")) return "Apple";
134
+ if (ua.includes("android")) {
135
+ const match = ua.match(/(?:^|\s)([a-z]+)(?:\s|$)/);
136
+ return match ? match[1].charAt(0).toUpperCase() + match[1].slice(1) : void 0;
137
+ }
138
+ return void 0;
139
+ }
140
+ /**
141
+ * Get model from user agent
142
+ */
143
+ getModel() {
144
+ const ua = navigator.userAgent;
145
+ const match = ua.match(/(iPhone|iPad|Android)[\s\/]+([\w]+)/i);
146
+ return match ? match[2] : void 0;
147
+ }
148
+ /**
149
+ * Get brand from user agent
150
+ */
151
+ getBrand() {
152
+ return this.getManufacturer();
153
+ }
154
+ /**
155
+ * Get device type
156
+ */
157
+ getDevice() {
158
+ const ua = navigator.userAgent.toLowerCase();
159
+ if (ua.includes("mobile")) return "mobile";
160
+ if (ua.includes("tablet")) return "tablet";
161
+ return "desktop";
162
+ }
163
+ /**
164
+ * Get product name
165
+ */
166
+ getProduct() {
167
+ const ua = navigator.userAgent;
168
+ const match = ua.match(/(iPhone|iPad|Android|Windows|Mac|Linux)/i);
169
+ return match ? match[1] : void 0;
170
+ }
171
+ /**
172
+ * Get hardware info
173
+ */
174
+ getHardware() {
175
+ const ua = navigator.userAgent;
176
+ const match = ua.match(/\(([^)]+)\)/);
177
+ return match ? match[1] : void 0;
178
+ }
179
+ /**
180
+ * Get OS version
181
+ */
182
+ getOSVersion() {
183
+ const ua = navigator.userAgent;
184
+ const patterns = [
185
+ /OS\s+([\d_]+)/i,
186
+ // iOS
187
+ /Android\s+([\d.]+)/i,
188
+ // Android
189
+ /Windows\s+([\d.]+)/i,
190
+ // Windows
191
+ /Mac\s+OS\s+X\s+([\d_]+)/i,
192
+ // macOS
193
+ /Linux/i
194
+ // Linux
195
+ ];
196
+ for (const pattern of patterns) {
197
+ const match = ua.match(pattern);
198
+ if (match) {
199
+ return match[1]?.replace(/_/g, ".") || match[0];
200
+ }
201
+ }
202
+ return void 0;
203
+ }
204
+ /**
205
+ * Get screen density
206
+ */
207
+ getScreenDensity() {
208
+ if (window.devicePixelRatio) {
209
+ return String(window.devicePixelRatio);
210
+ }
211
+ return void 0;
212
+ }
213
+ /**
214
+ * Hash a string using SHA-256-like algorithm (64 hex characters)
215
+ * Backend expects SHA-256 format (64 hex chars)
216
+ */
217
+ hashString(str) {
218
+ return this.sha256LikeHash(str);
219
+ }
220
+ /**
221
+ * SHA-256-like hash function (synchronous, deterministic)
222
+ * Produces 64-character hex string matching SHA-256 format
223
+ */
224
+ sha256LikeHash(str) {
225
+ const hashes = [];
226
+ let h1 = 5381;
227
+ for (let i = 0; i < str.length; i++) {
228
+ h1 = (h1 << 5) + h1 + str.charCodeAt(i);
229
+ h1 = h1 & 4294967295;
230
+ }
231
+ hashes.push(h1);
232
+ let h2 = 0;
233
+ for (let i = str.length - 1; i >= 0; i--) {
234
+ h2 = (h2 << 7) - h2 + str.charCodeAt(i);
235
+ h2 = h2 & 4294967295;
236
+ }
237
+ hashes.push(h2);
238
+ let h3 = 0;
239
+ for (let i = 0; i < str.length; i++) {
240
+ h3 = h3 ^ str.charCodeAt(i) << i % 4 * 8;
241
+ h3 = h3 & 4294967295;
242
+ }
243
+ hashes.push(h3);
244
+ let h4 = 0;
245
+ for (let i = 0; i < str.length; i++) {
246
+ h4 = h4 * 31 + str.charCodeAt(i) & 4294967295;
247
+ }
248
+ hashes.push(h4);
249
+ let h5 = 0;
250
+ for (let i = 0; i < str.length; i++) {
251
+ const rotated = (str.charCodeAt(i) << i % 16 | str.charCodeAt(i) >>> 32 - i % 16) & 4294967295;
252
+ h5 = h5 + rotated & 4294967295;
253
+ }
254
+ hashes.push(h5);
255
+ let h6 = 2166136261;
256
+ for (let i = 0; i < str.length; i++) {
257
+ h6 = (h6 ^ str.charCodeAt(i)) * 16777619;
258
+ h6 = h6 & 4294967295;
259
+ }
260
+ hashes.push(h6);
261
+ let h7 = 0;
262
+ for (let i = 0; i < str.length; i++) {
263
+ h7 = h7 + str.charCodeAt(i) * (i + 1) & 4294967295;
264
+ }
265
+ hashes.push(h7);
266
+ let h8 = 0;
267
+ for (let i = 0; i < str.length; i += 2) {
268
+ const combined2 = str.charCodeAt(i) + (str.charCodeAt(i + 1) || 0) * 256;
269
+ h8 = (h8 << 3) - h8 + combined2;
270
+ h8 = h8 & 4294967295;
271
+ }
272
+ hashes.push(h8);
273
+ const hexParts = hashes.map((h) => Math.abs(h).toString(16).padStart(8, "0"));
274
+ const combined = hexParts.join("");
275
+ return combined.substring(0, 64).padEnd(64, "0");
276
+ }
277
+ /**
278
+ * Clear cached device info (useful for testing)
279
+ */
280
+ clearCache() {
281
+ this.cachedDeviceInfo = null;
282
+ }
283
+ };
284
+
285
+ // ../fraud-sdk/src/collectors/kinematic-engine.ts
286
+ var VELOCITY_SPIKE_THRESHOLD = 2.5;
287
+ var WINDOW_LIMIT = 200;
288
+ var KinematicEngine = class {
289
+ constructor(options = {}) {
290
+ this.featureVectors = [];
291
+ this.pointerQueue = [];
292
+ this.lastVelocity = 0;
293
+ this.lastAcceleration = 0;
294
+ this.lastAngle = 0;
295
+ this.lastPoint = null;
296
+ this.secondLastPoint = null;
297
+ this.rafId = null;
298
+ this.maxSwipeVelocity = 0;
299
+ this.isActive = false;
300
+ // Enhanced tracking
301
+ this.clickCount = 0;
302
+ this.rightClickCount = 0;
303
+ this.doubleClickCount = 0;
304
+ this.totalMouseDistance = 0;
305
+ this.lastClickTime = 0;
306
+ this.scrollDistance = 0;
307
+ this.lastScrollTop = 0;
308
+ this.touchEventCount = 0;
309
+ this.mouseMoveHandler = (event) => this.enqueuePoint(event, "move");
310
+ this.mouseDownHandler = (event) => {
311
+ this.enqueuePoint(event, "down");
312
+ this.handleClick(event);
313
+ };
314
+ this.mouseUpHandler = (event) => this.enqueuePoint(event, "up");
315
+ this.contextMenuHandler = (event) => this.handleRightClick(event);
316
+ this.scrollHandler = () => this.handleScroll();
317
+ this.touchStartHandler = () => this.handleTouch();
318
+ this.touchMoveHandler = () => this.handleTouch();
319
+ this.touchEndHandler = () => this.handleTouch();
320
+ this.onEvent = options.onEvent;
321
+ this.maskValue = options.maskValue ?? -1;
322
+ }
323
+ start() {
324
+ if (this.isActive || typeof document === "undefined") return;
325
+ document.addEventListener("mousemove", this.mouseMoveHandler, { passive: true });
326
+ document.addEventListener("mousedown", this.mouseDownHandler, { passive: true });
327
+ document.addEventListener("mouseup", this.mouseUpHandler, { passive: true });
328
+ document.addEventListener("contextmenu", this.contextMenuHandler, { passive: true });
329
+ window.addEventListener("scroll", this.scrollHandler, { passive: true });
330
+ document.addEventListener("touchstart", this.touchStartHandler, { passive: true });
331
+ document.addEventListener("touchmove", this.touchMoveHandler, { passive: true });
332
+ document.addEventListener("touchend", this.touchEndHandler, { passive: true });
333
+ this.lastScrollTop = window.pageYOffset || document.documentElement.scrollTop;
334
+ this.isActive = true;
335
+ }
336
+ stop() {
337
+ if (!this.isActive || typeof document === "undefined") return;
338
+ document.removeEventListener("mousemove", this.mouseMoveHandler);
339
+ document.removeEventListener("mousedown", this.mouseDownHandler);
340
+ document.removeEventListener("mouseup", this.mouseUpHandler);
341
+ document.removeEventListener("contextmenu", this.contextMenuHandler);
342
+ window.removeEventListener("scroll", this.scrollHandler);
343
+ document.removeEventListener("touchstart", this.touchStartHandler);
344
+ document.removeEventListener("touchmove", this.touchMoveHandler);
345
+ document.removeEventListener("touchend", this.touchEndHandler);
346
+ if (this.rafId !== null) {
347
+ cancelAnimationFrame(this.rafId);
348
+ this.rafId = null;
349
+ }
350
+ this.isActive = false;
351
+ }
352
+ getVectors() {
353
+ return this.featureVectors;
354
+ }
355
+ getSuspiciousSwipeVelocity() {
356
+ return this.maxSwipeVelocity;
357
+ }
358
+ /**
359
+ * Get enhanced mouse/touch signals
360
+ */
361
+ getMouseSignals() {
362
+ const averageVelocity = this.featureVectors.length > 0 ? this.featureVectors.reduce((sum, v) => sum + v.velocity, 0) / this.featureVectors.length : 0;
363
+ const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
364
+ const scrollDiff = Math.abs(currentScrollTop - this.lastScrollTop);
365
+ const scrollDirection = currentScrollTop > this.lastScrollTop ? "down" : "up";
366
+ return {
367
+ totalDistance: this.totalMouseDistance,
368
+ averageVelocity,
369
+ clickCount: this.clickCount,
370
+ rightClickCount: this.rightClickCount,
371
+ doubleClickCount: this.doubleClickCount,
372
+ scrollDistance: this.scrollDistance + scrollDiff,
373
+ scrollDirection,
374
+ touchEvents: this.touchEventCount
375
+ };
376
+ }
377
+ reset() {
378
+ this.featureVectors = [];
379
+ this.pointerQueue = [];
380
+ this.lastVelocity = 0;
381
+ this.lastAcceleration = 0;
382
+ this.lastAngle = 0;
383
+ this.lastPoint = null;
384
+ this.secondLastPoint = null;
385
+ this.maxSwipeVelocity = 0;
386
+ this.clickCount = 0;
387
+ this.rightClickCount = 0;
388
+ this.doubleClickCount = 0;
389
+ this.totalMouseDistance = 0;
390
+ this.lastClickTime = 0;
391
+ this.scrollDistance = 0;
392
+ this.lastScrollTop = 0;
393
+ this.touchEventCount = 0;
394
+ }
395
+ enqueuePoint(event, type) {
396
+ const point = {
397
+ x: event.clientX,
398
+ y: event.clientY,
399
+ timestamp: performance.now(),
400
+ type
401
+ };
402
+ this.pointerQueue.push(point);
403
+ if (this.pointerQueue.length > WINDOW_LIMIT) {
404
+ this.pointerQueue.shift();
405
+ }
406
+ if (this.onEvent) {
407
+ this.onEvent(type === "move" ? "mousemove" : type === "down" ? "mousedown" : "mouseup");
408
+ }
409
+ this.scheduleProcess();
410
+ }
411
+ scheduleProcess() {
412
+ if (this.rafId !== null) return;
413
+ this.rafId = requestAnimationFrame(() => {
414
+ this.rafId = null;
415
+ this.processQueue();
416
+ if (this.pointerQueue.length > 0) {
417
+ this.scheduleProcess();
418
+ }
419
+ });
420
+ }
421
+ processQueue() {
422
+ while (this.pointerQueue.length > 0) {
423
+ const point = this.pointerQueue.shift();
424
+ this.computeFeatures(point);
425
+ this.secondLastPoint = this.lastPoint;
426
+ this.lastPoint = point;
427
+ }
428
+ }
429
+ computeFeatures(point) {
430
+ if (!this.lastPoint) {
431
+ this.lastPoint = point;
432
+ this.lastAngle = 0;
433
+ return;
434
+ }
435
+ const dt = point.timestamp - this.lastPoint.timestamp;
436
+ if (dt <= 0) {
437
+ return;
438
+ }
439
+ const dx = point.x - this.lastPoint.x;
440
+ const dy = point.y - this.lastPoint.y;
441
+ const distance = Math.sqrt(dx * dx + dy * dy);
442
+ this.totalMouseDistance += distance;
443
+ const velocity = distance / dt;
444
+ const acceleration = (velocity - this.lastVelocity) / dt;
445
+ const jerk = (acceleration - this.lastAcceleration) / dt;
446
+ const angle = Math.atan2(dy, dx);
447
+ const angularVelocity = this.normalizeAngle(angle - this.lastAngle) / dt;
448
+ const curvature = this.calculateCurvature(point);
449
+ this.featureVectors.push({
450
+ timestamp: point.timestamp,
451
+ velocity,
452
+ acceleration,
453
+ jerk,
454
+ curvature,
455
+ angularVelocity
456
+ });
457
+ this.maxSwipeVelocity = Math.max(this.maxSwipeVelocity, velocity);
458
+ if (velocity > VELOCITY_SPIKE_THRESHOLD) {
459
+ this.featureVectors.push({
460
+ timestamp: point.timestamp,
461
+ velocity,
462
+ acceleration,
463
+ jerk,
464
+ curvature,
465
+ angularVelocity: angularVelocity * 1.2
466
+ });
467
+ }
468
+ if (this.featureVectors.length > WINDOW_LIMIT) {
469
+ this.featureVectors.splice(0, this.featureVectors.length - WINDOW_LIMIT);
470
+ }
471
+ this.lastVelocity = velocity;
472
+ this.lastAcceleration = acceleration;
473
+ this.lastAngle = angle;
474
+ }
475
+ calculateCurvature(point) {
476
+ if (!this.lastPoint || !this.secondLastPoint) return 0;
477
+ const p0 = this.secondLastPoint;
478
+ const p1 = this.lastPoint;
479
+ const p2 = point;
480
+ const chordLength = Math.hypot(p2.x - p0.x, p2.y - p0.y);
481
+ const pathLength = Math.hypot(p1.x - p0.x, p1.y - p0.y) + Math.hypot(p2.x - p1.x, p2.y - p1.y);
482
+ if (chordLength === 0) return 0;
483
+ return (pathLength - chordLength) / chordLength;
484
+ }
485
+ normalizeAngle(angleDiff) {
486
+ while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
487
+ while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
488
+ return angleDiff;
489
+ }
490
+ handleClick(_event) {
491
+ const now = performance.now();
492
+ if (now - this.lastClickTime < 500) {
493
+ this.doubleClickCount++;
494
+ } else {
495
+ this.clickCount++;
496
+ }
497
+ this.lastClickTime = now;
498
+ }
499
+ handleRightClick(_event) {
500
+ this.rightClickCount++;
501
+ }
502
+ handleScroll() {
503
+ const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
504
+ const scrollDiff = Math.abs(currentScrollTop - this.lastScrollTop);
505
+ this.scrollDistance += scrollDiff;
506
+ this.lastScrollTop = currentScrollTop;
507
+ }
508
+ handleTouch() {
509
+ this.touchEventCount++;
510
+ }
511
+ };
512
+
513
+ // ../fraud-sdk/src/collectors/keystroke-monitor.ts
514
+ var MAX_BUFFER = 200;
515
+ var KeystrokeMonitor = class {
516
+ constructor(options = {}) {
517
+ this.keyDownTimes = /* @__PURE__ */ new Map();
518
+ this.lastKeyUpTime = null;
519
+ this.lastKeyDownTime = null;
520
+ this.featureVectors = [];
521
+ this.dwellTimes = [];
522
+ this.flightTimes = [];
523
+ this.digraphLatencies = [];
524
+ this.isActive = false;
525
+ // Enhanced tracking
526
+ this.keydownCount = 0;
527
+ this.keyupCount = 0;
528
+ this.specialKeyCount = 0;
529
+ this.lastKeystrokeTime = null;
530
+ this.pauseCount = 0;
531
+ this.PAUSE_THRESHOLD = 2e3;
532
+ // 2 seconds
533
+ this.keystrokeTimestamps = [];
534
+ this.keyDownHandler = (event) => this.handleKeyDown(event);
535
+ this.keyUpHandler = (event) => this.handleKeyUp(event);
536
+ this.onEvent = options.onEvent;
537
+ }
538
+ start() {
539
+ if (this.isActive || typeof document === "undefined") return;
540
+ document.addEventListener("keydown", this.keyDownHandler, { passive: true, capture: true });
541
+ document.addEventListener("keyup", this.keyUpHandler, { passive: true, capture: true });
542
+ this.isActive = true;
543
+ }
544
+ stop() {
545
+ if (!this.isActive || typeof document === "undefined") return;
546
+ document.removeEventListener("keydown", this.keyDownHandler, true);
547
+ document.removeEventListener("keyup", this.keyUpHandler, true);
548
+ this.isActive = false;
549
+ }
550
+ getVectors() {
551
+ return this.featureVectors;
552
+ }
553
+ getDwellTimes(limit = 50) {
554
+ return this.dwellTimes.slice(-limit);
555
+ }
556
+ getFlightTimes(limit = 50) {
557
+ return this.flightTimes.slice(-limit);
558
+ }
559
+ /**
560
+ * Get enhanced keyboard signals
561
+ */
562
+ getKeyboardSignals() {
563
+ let typingSpeed = 0;
564
+ if (this.keystrokeTimestamps.length > 1) {
565
+ const timeSpan = this.keystrokeTimestamps[this.keystrokeTimestamps.length - 1] - this.keystrokeTimestamps[0];
566
+ const minutes = timeSpan / 6e4;
567
+ if (minutes > 0) {
568
+ typingSpeed = this.keystrokeTimestamps.length / minutes;
569
+ }
570
+ }
571
+ const totalKeystrokes = this.keydownCount;
572
+ const backspaceRatio = totalKeystrokes > 0 ? this.getBackspaceCount() / totalKeystrokes : 0;
573
+ return {
574
+ keydownCount: this.keydownCount,
575
+ keyupCount: this.keyupCount,
576
+ specialKeyCount: this.specialKeyCount,
577
+ typingSpeed,
578
+ pauseCount: this.pauseCount,
579
+ backspaceRatio
580
+ };
581
+ }
582
+ getBackspaceCount() {
583
+ return 0;
584
+ }
585
+ reset() {
586
+ this.keyDownTimes.clear();
587
+ this.lastKeyUpTime = null;
588
+ this.lastKeyDownTime = null;
589
+ this.featureVectors = [];
590
+ this.dwellTimes = [];
591
+ this.flightTimes = [];
592
+ this.digraphLatencies = [];
593
+ this.keydownCount = 0;
594
+ this.keyupCount = 0;
595
+ this.specialKeyCount = 0;
596
+ this.lastKeystrokeTime = null;
597
+ this.pauseCount = 0;
598
+ this.keystrokeTimestamps = [];
599
+ }
600
+ handleKeyDown(event) {
601
+ if (!this.isTargetField(event)) return;
602
+ const now = performance.now();
603
+ this.keydownCount++;
604
+ this.keystrokeTimestamps.push(now);
605
+ if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) {
606
+ this.specialKeyCount++;
607
+ }
608
+ if (this.lastKeystrokeTime !== null && now - this.lastKeystrokeTime > this.PAUSE_THRESHOLD) {
609
+ this.pauseCount++;
610
+ }
611
+ this.lastKeystrokeTime = now;
612
+ if (this.lastKeyUpTime !== null) {
613
+ const flight = now - this.lastKeyUpTime;
614
+ this.flightTimes.push(flight);
615
+ this.appendVector({ dwellTime: -1, flightTime: flight, digraphLatency: -1, timestamp: now });
616
+ }
617
+ if (this.lastKeyDownTime !== null) {
618
+ const digraphLatency = now - this.lastKeyDownTime;
619
+ this.digraphLatencies.push(digraphLatency);
620
+ this.appendVector({ dwellTime: -1, flightTime: -1, digraphLatency, timestamp: now });
621
+ }
622
+ this.lastKeyDownTime = now;
623
+ this.keyDownTimes.set(event.code, now);
624
+ if (this.onEvent) this.onEvent("keydown");
625
+ }
626
+ handleKeyUp(event) {
627
+ if (!this.isTargetField(event)) return;
628
+ const now = performance.now();
629
+ this.keyupCount++;
630
+ const start = this.keyDownTimes.get(event.code);
631
+ if (start !== void 0) {
632
+ const dwell = now - start;
633
+ this.dwellTimes.push(dwell);
634
+ this.appendVector({ dwellTime: dwell, flightTime: -1, digraphLatency: -1, timestamp: now });
635
+ this.keyDownTimes.delete(event.code);
636
+ }
637
+ this.lastKeyUpTime = now;
638
+ if (this.onEvent) this.onEvent("keyup");
639
+ }
640
+ appendVector(vector) {
641
+ this.featureVectors.push(vector);
642
+ if (this.featureVectors.length > MAX_BUFFER) {
643
+ this.featureVectors.splice(0, this.featureVectors.length - MAX_BUFFER);
644
+ }
645
+ }
646
+ isTargetField(event) {
647
+ const target = event.target;
648
+ if (!target) return false;
649
+ const isEditable = target.isContentEditable || ["INPUT", "TEXTAREA"].includes(target.tagName);
650
+ return isEditable;
651
+ }
652
+ };
653
+
654
+ // ../fraud-sdk/src/collectors/behavioral.ts
655
+ var WINDOW_SIZE = 50;
656
+ var MASK_VALUE = -1;
657
+ var FEATURE_NAMES = [
658
+ "velocity",
659
+ "acceleration",
660
+ "jerk",
661
+ "curvature",
662
+ "angularVelocity",
663
+ "dwell",
664
+ "flight",
665
+ "digraphLatency"
666
+ ];
667
+ var BehavioralCollector = class {
668
+ constructor() {
669
+ this.isActive = false;
670
+ this.sessionEvents = [];
671
+ this.trackEvent = (event) => {
672
+ this.sessionEvents.push({ type: event, timestamp: performance.now() });
673
+ if (this.sessionEvents.length > 500) {
674
+ this.sessionEvents.shift();
675
+ }
676
+ };
677
+ this.kinematicEngine = new KinematicEngine({ onEvent: this.trackEvent });
678
+ this.keystrokeMonitor = new KeystrokeMonitor({ onEvent: this.trackEvent });
679
+ }
680
+ start() {
681
+ if (this.isActive) return;
682
+ this.kinematicEngine.start();
683
+ this.keystrokeMonitor.start();
684
+ this.isActive = true;
685
+ }
686
+ stop() {
687
+ if (!this.isActive) return;
688
+ this.kinematicEngine.stop();
689
+ this.keystrokeMonitor.stop();
690
+ this.isActive = false;
691
+ }
692
+ reset() {
693
+ this.sessionEvents = [];
694
+ this.kinematicEngine.reset();
695
+ this.keystrokeMonitor.reset();
696
+ }
697
+ getData() {
698
+ const kinematicVectors = this.kinematicEngine.getVectors();
699
+ const keystrokeVectors = this.keystrokeMonitor.getVectors();
700
+ const merged = [
701
+ ...kinematicVectors.map((v) => ({
702
+ timestamp: v.timestamp,
703
+ values: [
704
+ v.velocity,
705
+ v.acceleration,
706
+ v.jerk,
707
+ v.curvature,
708
+ v.angularVelocity,
709
+ MASK_VALUE,
710
+ MASK_VALUE,
711
+ MASK_VALUE
712
+ ]
713
+ })),
714
+ ...keystrokeVectors.map((v) => ({
715
+ timestamp: v.timestamp,
716
+ values: [
717
+ MASK_VALUE,
718
+ MASK_VALUE,
719
+ MASK_VALUE,
720
+ MASK_VALUE,
721
+ MASK_VALUE,
722
+ v.dwellTime,
723
+ v.flightTime,
724
+ v.digraphLatency
725
+ ]
726
+ }))
727
+ ].sort((a, b) => a.timestamp - b.timestamp);
728
+ const sequence = this.buildSequence(merged);
729
+ const suspiciousSwipeVelocity = this.kinematicEngine.getSuspiciousSwipeVelocity();
730
+ const entropy = this.calculateSessionEntropy();
731
+ const dwellTimes = this.keystrokeMonitor.getDwellTimes();
732
+ const flightTimes = this.keystrokeMonitor.getFlightTimes();
733
+ return {
734
+ typing_dwell_ms: dwellTimes,
735
+ typing_flight_ms: flightTimes,
736
+ swipe_velocity: suspiciousSwipeVelocity,
737
+ suspicious_swipe_velocity: suspiciousSwipeVelocity,
738
+ session_entropy: entropy,
739
+ behavioral_vectors: sequence
740
+ };
741
+ }
742
+ buildSequence(vectors) {
743
+ const padded = [];
744
+ const startIndex = Math.max(0, vectors.length - WINDOW_SIZE);
745
+ const window2 = vectors.slice(startIndex);
746
+ for (const vector of window2) {
747
+ padded.push(vector.values);
748
+ }
749
+ while (padded.length < WINDOW_SIZE) {
750
+ padded.unshift(new Array(FEATURE_NAMES.length).fill(MASK_VALUE));
751
+ }
752
+ return {
753
+ featureNames: [...FEATURE_NAMES],
754
+ windowSize: WINDOW_SIZE,
755
+ maskValue: MASK_VALUE,
756
+ sequence: padded
757
+ };
758
+ }
759
+ calculateSessionEntropy() {
760
+ if (this.sessionEvents.length === 0) return 0;
761
+ const counts = {};
762
+ this.sessionEvents.forEach((evt) => {
763
+ counts[evt.type] = (counts[evt.type] || 0) + 1;
764
+ });
765
+ const total = this.sessionEvents.length;
766
+ return Object.values(counts).reduce((entropy, count) => {
767
+ const probability = count / total;
768
+ return probability > 0 ? entropy - probability * Math.log2(probability) : entropy;
769
+ }, 0);
770
+ }
771
+ /**
772
+ * Get mouse signals from kinematic engine
773
+ */
774
+ getMouseSignals() {
775
+ return this.kinematicEngine.getMouseSignals();
776
+ }
777
+ /**
778
+ * Get keyboard signals from keystroke monitor
779
+ */
780
+ getKeyboardSignals() {
781
+ return this.keystrokeMonitor.getKeyboardSignals();
782
+ }
783
+ };
784
+
785
+ // ../fraud-sdk/src/collectors/canvas-fingerprint.ts
786
+ var CanvasFingerprintCollector = class {
787
+ constructor() {
788
+ this.cachedFingerprint = null;
789
+ }
790
+ /**
791
+ * Generate canvas fingerprint
792
+ * Returns null if canvas is not available
793
+ */
794
+ collect() {
795
+ if (this.cachedFingerprint) {
796
+ return this.cachedFingerprint;
797
+ }
798
+ const startTime = performance.now();
799
+ try {
800
+ const canvas = document.createElement("canvas");
801
+ canvas.width = 200;
802
+ canvas.height = 50;
803
+ const ctx = canvas.getContext("2d");
804
+ if (!ctx) {
805
+ return null;
806
+ }
807
+ ctx.textBaseline = "top";
808
+ ctx.font = "14px Arial";
809
+ ctx.textBaseline = "alphabetic";
810
+ ctx.fillStyle = "#f60";
811
+ ctx.fillRect(125, 1, 62, 20);
812
+ ctx.fillStyle = "#069";
813
+ ctx.fillText("KeverdFingerprint", 2, 15);
814
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
815
+ ctx.fillText("KeverdFingerprint", 4, 17);
816
+ const gradient = ctx.createLinearGradient(0, 0, 200, 50);
817
+ gradient.addColorStop(0, "rgba(255, 0, 0, 0.5)");
818
+ gradient.addColorStop(1, "rgba(0, 0, 255, 0.5)");
819
+ ctx.fillStyle = gradient;
820
+ ctx.fillRect(0, 0, 200, 50);
821
+ ctx.strokeStyle = "#000";
822
+ ctx.lineWidth = 2;
823
+ ctx.beginPath();
824
+ ctx.arc(100, 25, 20, 0, Math.PI * 2);
825
+ ctx.stroke();
826
+ ctx.font = "12px Verdana";
827
+ ctx.fillStyle = "#000";
828
+ ctx.fillText("CanvasFP", 150, 30);
829
+ const dataURL = canvas.toDataURL();
830
+ const hash = this.hashString(dataURL);
831
+ const duration = performance.now() - startTime;
832
+ this.cachedFingerprint = {
833
+ value: hash,
834
+ duration: Math.round(duration * 100) / 100
835
+ };
836
+ return this.cachedFingerprint;
837
+ } catch (error) {
838
+ console.warn("[Keverd SDK] Canvas fingerprinting failed:", error);
839
+ return null;
840
+ }
841
+ }
842
+ /**
843
+ * Hash a string to create a stable fingerprint
844
+ */
845
+ hashString(str) {
846
+ let hash = 0;
847
+ for (let i = 0; i < str.length; i++) {
848
+ const char = str.charCodeAt(i);
849
+ hash = (hash << 5) - hash + char;
850
+ hash = hash & hash;
851
+ }
852
+ return Math.abs(hash).toString(16).padStart(8, "0");
853
+ }
854
+ /**
855
+ * Clear cached fingerprint
856
+ */
857
+ clearCache() {
858
+ this.cachedFingerprint = null;
859
+ }
860
+ };
861
+
862
+ // ../fraud-sdk/src/collectors/webgl-fingerprint.ts
863
+ var WebGLFingerprintCollector = class {
864
+ constructor() {
865
+ this.cachedFingerprint = null;
866
+ }
867
+ /**
868
+ * Generate WebGL fingerprint
869
+ * Returns null if WebGL is not available
870
+ */
871
+ collect() {
872
+ if (this.cachedFingerprint) {
873
+ return this.cachedFingerprint;
874
+ }
875
+ const startTime = performance.now();
876
+ try {
877
+ const canvas = document.createElement("canvas");
878
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
879
+ if (!gl) {
880
+ return null;
881
+ }
882
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
883
+ let vendor = null;
884
+ let renderer = null;
885
+ if (debugInfo) {
886
+ vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || null;
887
+ renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || null;
888
+ } else {
889
+ vendor = gl.getParameter(gl.VENDOR) || null;
890
+ renderer = gl.getParameter(gl.RENDERER) || null;
891
+ }
892
+ const version = gl.getParameter(gl.VERSION) || null;
893
+ const shadingLanguageVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION) || null;
894
+ const extensions = [];
895
+ try {
896
+ const supportedExtensions = gl.getSupportedExtensions();
897
+ if (supportedExtensions) {
898
+ extensions.push(...supportedExtensions);
899
+ }
900
+ } catch (e) {
901
+ }
902
+ const parameters = {
903
+ maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
904
+ maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
905
+ maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS),
906
+ maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),
907
+ maxCombinedTextureImageUnits: gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),
908
+ maxFragmentUniformVectors: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
909
+ maxVaryingVectors: gl.getParameter(gl.MAX_VARYING_VECTORS),
910
+ aliasedLineWidthRange: gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE),
911
+ aliasedPointSizeRange: gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)
912
+ };
913
+ const precision = {};
914
+ try {
915
+ const vertexShader = gl.createShader(gl.VERTEX_SHADER);
916
+ if (vertexShader) {
917
+ gl.shaderSource(vertexShader, "precision mediump float;");
918
+ gl.compileShader(vertexShader);
919
+ const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
920
+ if (fragmentShader) {
921
+ gl.shaderSource(fragmentShader, "precision mediump float;");
922
+ gl.compileShader(fragmentShader);
923
+ precision.float = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT)?.precision?.toString() || "unknown";
924
+ precision.int = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT)?.precision?.toString() || "unknown";
925
+ }
926
+ }
927
+ } catch (e) {
928
+ }
929
+ const duration = performance.now() - startTime;
930
+ this.cachedFingerprint = {
931
+ vendor,
932
+ renderer,
933
+ version,
934
+ shadingLanguageVersion,
935
+ extensions: extensions.sort(),
936
+ // Sort for consistency
937
+ parameters,
938
+ precision,
939
+ duration: Math.round(duration * 100) / 100
940
+ };
941
+ return this.cachedFingerprint;
942
+ } catch (error) {
943
+ console.warn("[Keverd SDK] WebGL fingerprinting failed:", error);
944
+ return null;
945
+ }
946
+ }
947
+ /**
948
+ * Clear cached fingerprint
949
+ */
950
+ clearCache() {
951
+ this.cachedFingerprint = null;
952
+ }
953
+ };
954
+
955
+ // ../fraud-sdk/src/collectors/audio-fingerprint.ts
956
+ var AudioFingerprintCollector = class {
957
+ constructor() {
958
+ this.cachedFingerprint = null;
959
+ }
960
+ /**
961
+ * Generate audio fingerprint
962
+ * Returns null if audio context is not available
963
+ */
964
+ async collect() {
965
+ if (this.cachedFingerprint) {
966
+ return this.cachedFingerprint;
967
+ }
968
+ const startTime = performance.now();
969
+ try {
970
+ const AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
971
+ if (!AudioContext) {
972
+ return null;
973
+ }
974
+ const context = new AudioContext();
975
+ const oscillator = context.createOscillator();
976
+ const analyser = context.createAnalyser();
977
+ const gainNode = context.createGain();
978
+ const scriptProcessor = context.createScriptProcessor(4096, 1, 1);
979
+ analyser.fftSize = 2048;
980
+ analyser.smoothingTimeConstant = 0.8;
981
+ oscillator.connect(analyser);
982
+ analyser.connect(scriptProcessor);
983
+ scriptProcessor.connect(gainNode);
984
+ gainNode.connect(context.destination);
985
+ oscillator.type = "triangle";
986
+ oscillator.frequency.value = 1e4;
987
+ return new Promise((resolve) => {
988
+ const audioData = [];
989
+ let sampleCount = 0;
990
+ const maxSamples = 5;
991
+ scriptProcessor.onaudioprocess = (event) => {
992
+ const inputBuffer = event.inputBuffer;
993
+ const inputData = inputBuffer.getChannelData(0);
994
+ let sum = 0;
995
+ for (let i = 0; i < inputData.length; i++) {
996
+ sum += inputData[i] * inputData[i];
997
+ }
998
+ const rms = Math.sqrt(sum / inputData.length);
999
+ audioData.push(rms);
1000
+ sampleCount++;
1001
+ if (sampleCount >= maxSamples) {
1002
+ oscillator.stop();
1003
+ context.close();
1004
+ const fingerprint = this.calculateFingerprint(audioData);
1005
+ const duration = performance.now() - startTime;
1006
+ this.cachedFingerprint = {
1007
+ value: fingerprint,
1008
+ duration: Math.round(duration * 100) / 100
1009
+ };
1010
+ resolve(this.cachedFingerprint);
1011
+ }
1012
+ };
1013
+ oscillator.start(0);
1014
+ gainNode.gain.value = 0;
1015
+ setTimeout(() => {
1016
+ if (!this.cachedFingerprint) {
1017
+ oscillator.stop();
1018
+ context.close();
1019
+ const fallbackFingerprint = this.getFallbackFingerprint(context);
1020
+ const duration = performance.now() - startTime;
1021
+ this.cachedFingerprint = {
1022
+ value: fallbackFingerprint,
1023
+ duration: Math.round(duration * 100) / 100
1024
+ };
1025
+ resolve(this.cachedFingerprint);
1026
+ }
1027
+ }, 1e3);
1028
+ });
1029
+ } catch (error) {
1030
+ console.warn("[Keverd SDK] Audio fingerprinting failed:", error);
1031
+ return null;
1032
+ }
1033
+ }
1034
+ /**
1035
+ * Calculate fingerprint from audio data
1036
+ */
1037
+ calculateFingerprint(audioData) {
1038
+ if (audioData.length === 0) {
1039
+ return 0;
1040
+ }
1041
+ const sum = audioData.reduce((a, b) => a + b, 0);
1042
+ const mean = sum / audioData.length;
1043
+ const variance = audioData.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / audioData.length;
1044
+ const stdDev = Math.sqrt(variance);
1045
+ const fingerprint = mean * 1e6 + stdDev * 1e3 + audioData.length;
1046
+ return Math.round(fingerprint * 100) / 100;
1047
+ }
1048
+ /**
1049
+ * Get fallback fingerprint when audio processing fails
1050
+ */
1051
+ getFallbackFingerprint(context) {
1052
+ const sampleRate = context.sampleRate || 44100;
1053
+ const state = context.state || "unknown";
1054
+ const hash = (sampleRate.toString() + state).split("").reduce((acc, char) => {
1055
+ return (acc << 5) - acc + char.charCodeAt(0);
1056
+ }, 0);
1057
+ return Math.abs(hash);
1058
+ }
1059
+ /**
1060
+ * Clear cached fingerprint
1061
+ */
1062
+ clearCache() {
1063
+ this.cachedFingerprint = null;
1064
+ }
1065
+ };
1066
+
1067
+ // ../fraud-sdk/src/collectors/font-detection.ts
1068
+ var FontDetectionCollector = class {
1069
+ constructor() {
1070
+ this.cachedFingerprint = null;
1071
+ // Common fonts to test
1072
+ this.testFonts = [
1073
+ "Arial",
1074
+ "Verdana",
1075
+ "Times New Roman",
1076
+ "Courier New",
1077
+ "Georgia",
1078
+ "Palatino",
1079
+ "Garamond",
1080
+ "Bookman",
1081
+ "Comic Sans MS",
1082
+ "Trebuchet MS",
1083
+ "Arial Black",
1084
+ "Impact",
1085
+ "Tahoma",
1086
+ "Lucida Console",
1087
+ "Lucida Sans Unicode",
1088
+ "MS Sans Serif",
1089
+ "MS Serif",
1090
+ "Symbol",
1091
+ "Webdings",
1092
+ "Wingdings",
1093
+ "Monaco",
1094
+ "Menlo",
1095
+ "Consolas",
1096
+ "Courier",
1097
+ "Helvetica",
1098
+ "Helvetica Neue",
1099
+ "Roboto",
1100
+ "Open Sans",
1101
+ "Lato",
1102
+ "Montserrat"
1103
+ ];
1104
+ }
1105
+ /**
1106
+ * Detect available fonts
1107
+ * Returns null if detection fails
1108
+ */
1109
+ collect() {
1110
+ if (this.cachedFingerprint) {
1111
+ return this.cachedFingerprint;
1112
+ }
1113
+ const startTime = performance.now();
1114
+ try {
1115
+ const canvas = document.createElement("canvas");
1116
+ const ctx = canvas.getContext("2d");
1117
+ if (!ctx) {
1118
+ return null;
1119
+ }
1120
+ const baseFont = "monospace";
1121
+ const testString = "mmmmmmmmmmlli";
1122
+ const testSize = "72px";
1123
+ ctx.font = `${testSize} ${baseFont}`;
1124
+ const baseWidth = ctx.measureText(testString).width;
1125
+ const availableFonts = [];
1126
+ const missingFonts = [];
1127
+ for (const font of this.testFonts) {
1128
+ ctx.font = `${testSize} ${font}, ${baseFont}`;
1129
+ const width = ctx.measureText(testString).width;
1130
+ if (Math.abs(width - baseWidth) > 1) {
1131
+ availableFonts.push(font);
1132
+ } else {
1133
+ missingFonts.push(font);
1134
+ }
1135
+ }
1136
+ const duration = performance.now() - startTime;
1137
+ this.cachedFingerprint = {
1138
+ availableFonts: availableFonts.sort(),
1139
+ missingFonts: missingFonts.sort(),
1140
+ duration: Math.round(duration * 100) / 100
1141
+ };
1142
+ return this.cachedFingerprint;
1143
+ } catch (error) {
1144
+ console.warn("[Keverd SDK] Font detection failed:", error);
1145
+ return null;
1146
+ }
1147
+ }
1148
+ /**
1149
+ * Clear cached fingerprint
1150
+ */
1151
+ clearCache() {
1152
+ this.cachedFingerprint = null;
1153
+ }
1154
+ };
1155
+
1156
+ // ../fraud-sdk/src/collectors/media-devices.ts
1157
+ var MediaDevicesCollector = class {
1158
+ constructor() {
1159
+ this.cachedFingerprint = null;
1160
+ }
1161
+ /**
1162
+ * Collect media device information
1163
+ * Returns null if MediaDevices API is not available
1164
+ */
1165
+ async collect() {
1166
+ if (this.cachedFingerprint) {
1167
+ return this.cachedFingerprint;
1168
+ }
1169
+ const startTime = performance.now();
1170
+ try {
1171
+ if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
1172
+ return null;
1173
+ }
1174
+ let hasPermission = false;
1175
+ try {
1176
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
1177
+ hasPermission = true;
1178
+ stream.getTracks().forEach((track) => track.stop());
1179
+ } catch (e) {
1180
+ hasPermission = false;
1181
+ }
1182
+ const devices = await navigator.mediaDevices.enumerateDevices();
1183
+ const audioInputs = [];
1184
+ const audioOutputs = [];
1185
+ const videoInputs = [];
1186
+ devices.forEach((device) => {
1187
+ const sanitizedDevice = {
1188
+ deviceId: device.deviceId,
1189
+ kind: device.kind,
1190
+ label: hasPermission ? device.label : "",
1191
+ // Only include label if permission granted
1192
+ groupId: device.groupId,
1193
+ toJSON: () => ({
1194
+ deviceId: device.deviceId,
1195
+ kind: device.kind,
1196
+ label: hasPermission ? device.label : "",
1197
+ groupId: device.groupId
1198
+ })
1199
+ };
1200
+ switch (device.kind) {
1201
+ case "audioinput":
1202
+ audioInputs.push(sanitizedDevice);
1203
+ break;
1204
+ case "audiooutput":
1205
+ audioOutputs.push(sanitizedDevice);
1206
+ break;
1207
+ case "videoinput":
1208
+ videoInputs.push(sanitizedDevice);
1209
+ break;
1210
+ }
1211
+ });
1212
+ const duration = performance.now() - startTime;
1213
+ this.cachedFingerprint = {
1214
+ audioInputs,
1215
+ audioOutputs,
1216
+ videoInputs,
1217
+ hasPermission,
1218
+ duration: Math.round(duration * 100) / 100
1219
+ };
1220
+ return this.cachedFingerprint;
1221
+ } catch (error) {
1222
+ console.warn("[Keverd SDK] Media devices enumeration failed:", error);
1223
+ return null;
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Clear cached fingerprint
1228
+ */
1229
+ clearCache() {
1230
+ this.cachedFingerprint = null;
1231
+ }
1232
+ };
1233
+
1234
+ // ../fraud-sdk/src/collectors/screen-display.ts
1235
+ var ScreenDisplayCollector = class {
1236
+ constructor() {
1237
+ this.cachedFingerprint = null;
1238
+ }
1239
+ /**
1240
+ * Collect screen and display information
1241
+ */
1242
+ collect() {
1243
+ if (this.cachedFingerprint) {
1244
+ return this.cachedFingerprint;
1245
+ }
1246
+ const startTime = performance.now();
1247
+ const fingerprint = {
1248
+ width: screen.width,
1249
+ height: screen.height,
1250
+ availWidth: screen.availWidth,
1251
+ availHeight: screen.availHeight,
1252
+ colorDepth: screen.colorDepth,
1253
+ pixelDepth: screen.pixelDepth,
1254
+ pixelRatio: window.devicePixelRatio || 1,
1255
+ orientation: this.getOrientation(),
1256
+ orientationAngle: this.getOrientationAngle(),
1257
+ duration: 0
1258
+ };
1259
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1260
+ this.cachedFingerprint = fingerprint;
1261
+ return fingerprint;
1262
+ }
1263
+ /**
1264
+ * Get screen orientation
1265
+ */
1266
+ getOrientation() {
1267
+ if ("orientation" in screen) {
1268
+ const orientation = screen.orientation;
1269
+ if (orientation && orientation.type) {
1270
+ return orientation.type;
1271
+ }
1272
+ }
1273
+ if ("orientation" in window) {
1274
+ const angle = window.orientation;
1275
+ if (angle !== void 0) {
1276
+ if (angle === 0 || angle === 180) {
1277
+ return "portrait";
1278
+ } else if (angle === 90 || angle === -90) {
1279
+ return "landscape";
1280
+ }
1281
+ }
1282
+ }
1283
+ if (window.matchMedia) {
1284
+ if (window.matchMedia("(orientation: portrait)").matches) {
1285
+ return "portrait";
1286
+ } else if (window.matchMedia("(orientation: landscape)").matches) {
1287
+ return "landscape";
1288
+ }
1289
+ }
1290
+ return null;
1291
+ }
1292
+ /**
1293
+ * Get screen orientation angle
1294
+ */
1295
+ getOrientationAngle() {
1296
+ if ("orientation" in screen) {
1297
+ const orientation = screen.orientation;
1298
+ if (orientation && typeof orientation.angle === "number") {
1299
+ return orientation.angle;
1300
+ }
1301
+ }
1302
+ if ("orientation" in window) {
1303
+ const angle = window.orientation;
1304
+ if (typeof angle === "number") {
1305
+ return angle;
1306
+ }
1307
+ }
1308
+ return null;
1309
+ }
1310
+ /**
1311
+ * Clear cached fingerprint
1312
+ */
1313
+ clearCache() {
1314
+ this.cachedFingerprint = null;
1315
+ }
1316
+ };
1317
+
1318
+ // ../fraud-sdk/src/collectors/timezone-locale.ts
1319
+ var TimezoneLocaleCollector = class {
1320
+ constructor() {
1321
+ this.cachedFingerprint = null;
1322
+ }
1323
+ /**
1324
+ * Collect timezone and locale information
1325
+ */
1326
+ collect() {
1327
+ if (this.cachedFingerprint) {
1328
+ return this.cachedFingerprint;
1329
+ }
1330
+ const startTime = performance.now();
1331
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
1332
+ const timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
1333
+ const resolvedOptions = Intl.DateTimeFormat().resolvedOptions();
1334
+ const locale = resolvedOptions.locale;
1335
+ const locales = navigator.languages || [navigator.language];
1336
+ const dateFormat = this.getDateFormat();
1337
+ const timeFormat = this.getTimeFormat();
1338
+ const numberFormat = this.getNumberFormat();
1339
+ const calendar = resolvedOptions.calendar || null;
1340
+ const fingerprint = {
1341
+ timezone,
1342
+ timezoneOffset,
1343
+ locale,
1344
+ locales: locales.slice(0, 5),
1345
+ // Limit to first 5 locales
1346
+ dateFormat,
1347
+ timeFormat,
1348
+ numberFormat,
1349
+ calendar,
1350
+ duration: 0
1351
+ };
1352
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1353
+ this.cachedFingerprint = fingerprint;
1354
+ return fingerprint;
1355
+ }
1356
+ /**
1357
+ * Get date format preference
1358
+ */
1359
+ getDateFormat() {
1360
+ try {
1361
+ const formatter = new Intl.DateTimeFormat();
1362
+ const sample = formatter.format(new Date(2023, 0, 15));
1363
+ if (sample.includes("/")) {
1364
+ return "numeric";
1365
+ } else if (sample.includes("-")) {
1366
+ return "iso";
1367
+ } else if (sample.includes(".")) {
1368
+ return "european";
1369
+ }
1370
+ return "unknown";
1371
+ } catch {
1372
+ return "unknown";
1373
+ }
1374
+ }
1375
+ /**
1376
+ * Get time format preference (12h vs 24h)
1377
+ */
1378
+ getTimeFormat() {
1379
+ try {
1380
+ const formatter = new Intl.DateTimeFormat(void 0, {
1381
+ hour: "numeric",
1382
+ minute: "numeric"
1383
+ });
1384
+ const sample = formatter.format(new Date(2023, 0, 15, 13, 30));
1385
+ if (sample.toLowerCase().includes("am") || sample.toLowerCase().includes("pm")) {
1386
+ return "12h";
1387
+ }
1388
+ return "24h";
1389
+ } catch {
1390
+ return "unknown";
1391
+ }
1392
+ }
1393
+ /**
1394
+ * Get number format preference
1395
+ */
1396
+ getNumberFormat() {
1397
+ try {
1398
+ const formatter = new Intl.NumberFormat();
1399
+ const sample = formatter.format(1234.56);
1400
+ if (sample.includes(",")) {
1401
+ return "european";
1402
+ } else if (sample.includes(".")) {
1403
+ return "american";
1404
+ }
1405
+ return "unknown";
1406
+ } catch {
1407
+ return "unknown";
1408
+ }
1409
+ }
1410
+ /**
1411
+ * Clear cached fingerprint
1412
+ */
1413
+ clearCache() {
1414
+ this.cachedFingerprint = null;
1415
+ }
1416
+ };
1417
+
1418
+ // ../fraud-sdk/src/collectors/plugin-mime.ts
1419
+ var PluginMimeCollector = class {
1420
+ constructor() {
1421
+ this.cachedFingerprint = null;
1422
+ }
1423
+ /**
1424
+ * Collect plugin and MIME type information
1425
+ * Returns null if plugins are not available (modern browsers)
1426
+ */
1427
+ collect() {
1428
+ if (this.cachedFingerprint) {
1429
+ return this.cachedFingerprint;
1430
+ }
1431
+ const startTime = performance.now();
1432
+ try {
1433
+ if (!navigator.plugins || navigator.plugins.length === 0) {
1434
+ return null;
1435
+ }
1436
+ const plugins = [];
1437
+ const allMimeTypes = /* @__PURE__ */ new Set();
1438
+ for (let i = 0; i < Math.min(navigator.plugins.length, 10); i++) {
1439
+ const plugin = navigator.plugins[i];
1440
+ if (!plugin) continue;
1441
+ const mimeTypes = [];
1442
+ for (let j = 0; j < Math.min(plugin.length, 5); j++) {
1443
+ const mimeType = plugin[j];
1444
+ if (mimeType && mimeType.type) {
1445
+ mimeTypes.push(mimeType.type);
1446
+ allMimeTypes.add(mimeType.type);
1447
+ }
1448
+ }
1449
+ plugins.push({
1450
+ name: plugin.name || "Unknown",
1451
+ description: plugin.description || "",
1452
+ mimeTypes
1453
+ });
1454
+ }
1455
+ const fingerprint = {
1456
+ plugins,
1457
+ mimeTypes: Array.from(allMimeTypes).sort(),
1458
+ duration: 0
1459
+ };
1460
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1461
+ this.cachedFingerprint = fingerprint;
1462
+ return fingerprint;
1463
+ } catch (error) {
1464
+ console.warn("[Keverd SDK] Plugin/MIME collection failed:", error);
1465
+ return null;
1466
+ }
1467
+ }
1468
+ /**
1469
+ * Clear cached fingerprint
1470
+ */
1471
+ clearCache() {
1472
+ this.cachedFingerprint = null;
1473
+ }
1474
+ };
1475
+
1476
+ // ../fraud-sdk/src/collectors/battery-api.ts
1477
+ var BatteryAPICollector = class {
1478
+ constructor() {
1479
+ this.cachedFingerprint = null;
1480
+ }
1481
+ /**
1482
+ * Collect battery information
1483
+ * Returns null if Battery API is not available
1484
+ */
1485
+ async collect() {
1486
+ if (this.cachedFingerprint) {
1487
+ return this.cachedFingerprint;
1488
+ }
1489
+ const startTime = performance.now();
1490
+ try {
1491
+ const batteryManager = await this.getBatteryManager();
1492
+ if (!batteryManager) {
1493
+ return null;
1494
+ }
1495
+ const fingerprint = {
1496
+ level: batteryManager.level,
1497
+ charging: batteryManager.charging,
1498
+ chargingTime: batteryManager.chargingTime,
1499
+ dischargingTime: batteryManager.dischargingTime,
1500
+ duration: 0
1501
+ };
1502
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1503
+ this.cachedFingerprint = fingerprint;
1504
+ return fingerprint;
1505
+ } catch (error) {
1506
+ return null;
1507
+ }
1508
+ }
1509
+ /**
1510
+ * Get battery manager from various API locations
1511
+ */
1512
+ async getBatteryManager() {
1513
+ if ("getBattery" in navigator) {
1514
+ try {
1515
+ return await navigator.getBattery();
1516
+ } catch (e) {
1517
+ }
1518
+ }
1519
+ if ("webkitGetBattery" in navigator) {
1520
+ try {
1521
+ return await navigator.webkitGetBattery();
1522
+ } catch (e) {
1523
+ }
1524
+ }
1525
+ if ("mozGetBattery" in navigator) {
1526
+ try {
1527
+ return await navigator.mozGetBattery();
1528
+ } catch (e) {
1529
+ }
1530
+ }
1531
+ return null;
1532
+ }
1533
+ /**
1534
+ * Clear cached fingerprint
1535
+ */
1536
+ clearCache() {
1537
+ this.cachedFingerprint = null;
1538
+ }
1539
+ };
1540
+
1541
+ // ../fraud-sdk/src/collectors/storage-database.ts
1542
+ var StorageDatabaseCollector = class {
1543
+ constructor() {
1544
+ this.cachedFingerprint = null;
1545
+ }
1546
+ /**
1547
+ * Collect storage and database information
1548
+ */
1549
+ async collect() {
1550
+ if (this.cachedFingerprint) {
1551
+ return this.cachedFingerprint;
1552
+ }
1553
+ const startTime = performance.now();
1554
+ const fingerprint = {
1555
+ localStorage: await this.checkLocalStorage(),
1556
+ sessionStorage: await this.checkSessionStorage(),
1557
+ indexedDB: this.checkIndexedDB(),
1558
+ webSQL: this.checkWebSQL(),
1559
+ duration: 0
1560
+ };
1561
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1562
+ this.cachedFingerprint = fingerprint;
1563
+ return fingerprint;
1564
+ }
1565
+ /**
1566
+ * Check localStorage availability and quota
1567
+ */
1568
+ async checkLocalStorage() {
1569
+ let available = false;
1570
+ let quota = null;
1571
+ let usage = null;
1572
+ try {
1573
+ const test = "__localStorage_test__";
1574
+ localStorage.setItem(test, test);
1575
+ localStorage.removeItem(test);
1576
+ available = true;
1577
+ if ("storage" in navigator && "estimate" in navigator.storage) {
1578
+ try {
1579
+ const estimate = await navigator.storage.estimate();
1580
+ if (estimate) {
1581
+ quota = estimate.quota || null;
1582
+ usage = estimate.usage || null;
1583
+ }
1584
+ } catch (e) {
1585
+ }
1586
+ }
1587
+ } catch (e) {
1588
+ available = false;
1589
+ }
1590
+ return { available, quota, usage };
1591
+ }
1592
+ /**
1593
+ * Check sessionStorage availability and quota
1594
+ */
1595
+ async checkSessionStorage() {
1596
+ let available = false;
1597
+ let quota = null;
1598
+ let usage = null;
1599
+ try {
1600
+ const test = "__sessionStorage_test__";
1601
+ sessionStorage.setItem(test, test);
1602
+ sessionStorage.removeItem(test);
1603
+ available = true;
1604
+ if ("storage" in navigator && "estimate" in navigator.storage) {
1605
+ try {
1606
+ const estimate = await navigator.storage.estimate();
1607
+ if (estimate) {
1608
+ quota = estimate.quota || null;
1609
+ usage = estimate.usage || null;
1610
+ }
1611
+ } catch (e) {
1612
+ }
1613
+ }
1614
+ } catch (e) {
1615
+ available = false;
1616
+ }
1617
+ return { available, quota, usage };
1618
+ }
1619
+ /**
1620
+ * Check IndexedDB availability
1621
+ */
1622
+ checkIndexedDB() {
1623
+ return {
1624
+ available: "indexedDB" in window && !!window.indexedDB
1625
+ };
1626
+ }
1627
+ /**
1628
+ * Check WebSQL availability (deprecated but still in some browsers)
1629
+ */
1630
+ checkWebSQL() {
1631
+ return {
1632
+ available: "openDatabase" in window && !!window.openDatabase
1633
+ };
1634
+ }
1635
+ /**
1636
+ * Clear cached fingerprint
1637
+ */
1638
+ clearCache() {
1639
+ this.cachedFingerprint = null;
1640
+ }
1641
+ };
1642
+
1643
+ // ../fraud-sdk/src/collectors/cpu-memory.ts
1644
+ var CPUMemoryCollector = class {
1645
+ constructor() {
1646
+ this.cachedFingerprint = null;
1647
+ }
1648
+ /**
1649
+ * Collect CPU and memory information
1650
+ */
1651
+ collect() {
1652
+ if (this.cachedFingerprint) {
1653
+ return this.cachedFingerprint;
1654
+ }
1655
+ const startTime = performance.now();
1656
+ const fingerprint = {
1657
+ hardwareConcurrency: navigator.hardwareConcurrency || 0,
1658
+ deviceMemory: this.getDeviceMemory(),
1659
+ performanceMemory: this.getPerformanceMemory(),
1660
+ duration: 0
1661
+ };
1662
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1663
+ this.cachedFingerprint = fingerprint;
1664
+ return fingerprint;
1665
+ }
1666
+ /**
1667
+ * Get device memory (if available)
1668
+ */
1669
+ getDeviceMemory() {
1670
+ if ("deviceMemory" in navigator && navigator.deviceMemory) {
1671
+ return navigator.deviceMemory;
1672
+ }
1673
+ return null;
1674
+ }
1675
+ /**
1676
+ * Get performance memory information (Chrome-specific)
1677
+ */
1678
+ getPerformanceMemory() {
1679
+ if ("memory" in performance) {
1680
+ const memory = performance.memory;
1681
+ return {
1682
+ jsHeapSizeLimit: memory.jsHeapSizeLimit || null,
1683
+ totalJSHeapSize: memory.totalJSHeapSize || null,
1684
+ usedJSHeapSize: memory.usedJSHeapSize || null
1685
+ };
1686
+ }
1687
+ return {
1688
+ jsHeapSizeLimit: null,
1689
+ totalJSHeapSize: null,
1690
+ usedJSHeapSize: null
1691
+ };
1692
+ }
1693
+ /**
1694
+ * Clear cached fingerprint
1695
+ */
1696
+ clearCache() {
1697
+ this.cachedFingerprint = null;
1698
+ }
1699
+ };
1700
+
1701
+ // ../fraud-sdk/src/collectors/touch-pointer.ts
1702
+ var TouchPointerCollector = class {
1703
+ constructor() {
1704
+ this.cachedFingerprint = null;
1705
+ }
1706
+ /**
1707
+ * Collect touch and pointer information
1708
+ */
1709
+ collect() {
1710
+ if (this.cachedFingerprint) {
1711
+ return this.cachedFingerprint;
1712
+ }
1713
+ const startTime = performance.now();
1714
+ const fingerprint = {
1715
+ maxTouchPoints: navigator.maxTouchPoints || 0,
1716
+ touchSupport: this.checkTouchSupport(),
1717
+ pointerSupport: this.checkPointerSupport(),
1718
+ pointerTypes: this.getPointerTypes(),
1719
+ duration: 0
1720
+ };
1721
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1722
+ this.cachedFingerprint = fingerprint;
1723
+ return fingerprint;
1724
+ }
1725
+ /**
1726
+ * Check if touch events are supported
1727
+ */
1728
+ checkTouchSupport() {
1729
+ return "ontouchstart" in window || navigator.maxTouchPoints > 0 || !!window.DocumentTouch && document instanceof window.DocumentTouch;
1730
+ }
1731
+ /**
1732
+ * Check if Pointer Events are supported
1733
+ */
1734
+ checkPointerSupport() {
1735
+ return "PointerEvent" in window || "MSPointerEvent" in window;
1736
+ }
1737
+ /**
1738
+ * Get available pointer types
1739
+ */
1740
+ getPointerTypes() {
1741
+ const types = [];
1742
+ if ("PointerEvent" in window) {
1743
+ if (navigator.maxTouchPoints > 0) {
1744
+ types.push("touch");
1745
+ }
1746
+ types.push("mouse");
1747
+ if ("pen" in navigator) {
1748
+ types.push("pen");
1749
+ }
1750
+ }
1751
+ return types;
1752
+ }
1753
+ /**
1754
+ * Clear cached fingerprint
1755
+ */
1756
+ clearCache() {
1757
+ this.cachedFingerprint = null;
1758
+ }
1759
+ };
1760
+
1761
+ // ../fraud-sdk/src/collectors/permissions-api.ts
1762
+ var PermissionsAPICollector = class {
1763
+ constructor() {
1764
+ this.cachedFingerprint = null;
1765
+ }
1766
+ /**
1767
+ * Collect permission states
1768
+ * Returns null if Permissions API is not available
1769
+ */
1770
+ async collect() {
1771
+ if (this.cachedFingerprint) {
1772
+ return this.cachedFingerprint;
1773
+ }
1774
+ const startTime = performance.now();
1775
+ try {
1776
+ if (!("permissions" in navigator)) {
1777
+ return null;
1778
+ }
1779
+ const permissions = navigator.permissions;
1780
+ const fingerprint = {
1781
+ camera: await this.queryPermission(permissions, "camera"),
1782
+ microphone: await this.queryPermission(permissions, "microphone"),
1783
+ notifications: await this.queryPermission(permissions, "notifications"),
1784
+ geolocation: await this.queryPermission(permissions, "geolocation"),
1785
+ persistentStorage: await this.queryPermission(permissions, "persistent-storage"),
1786
+ duration: 0
1787
+ };
1788
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1789
+ this.cachedFingerprint = fingerprint;
1790
+ return fingerprint;
1791
+ } catch (error) {
1792
+ console.warn("[Keverd SDK] Permissions API collection failed:", error);
1793
+ return null;
1794
+ }
1795
+ }
1796
+ /**
1797
+ * Query a specific permission
1798
+ */
1799
+ async queryPermission(permissions, name) {
1800
+ try {
1801
+ const result = await permissions.query({ name });
1802
+ return result.state || null;
1803
+ } catch (e) {
1804
+ return null;
1805
+ }
1806
+ }
1807
+ /**
1808
+ * Clear cached fingerprint
1809
+ */
1810
+ clearCache() {
1811
+ this.cachedFingerprint = null;
1812
+ }
1813
+ };
1814
+
1815
+ // ../fraud-sdk/src/collectors/webrtc-ip.ts
1816
+ var WebRTCIPCollector = class {
1817
+ constructor() {
1818
+ this.cachedFingerprint = null;
1819
+ }
1820
+ /**
1821
+ * Collect WebRTC IP information
1822
+ * Returns null if WebRTC is not available or fails
1823
+ */
1824
+ async collect() {
1825
+ if (this.cachedFingerprint) {
1826
+ return this.cachedFingerprint;
1827
+ }
1828
+ const startTime = performance.now();
1829
+ try {
1830
+ const RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
1831
+ if (!RTCPeerConnection) {
1832
+ return null;
1833
+ }
1834
+ const localIPs = [];
1835
+ let publicIP = null;
1836
+ const pc = new RTCPeerConnection({
1837
+ iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
1838
+ });
1839
+ pc.onicecandidate = (event) => {
1840
+ if (event.candidate) {
1841
+ const candidate = event.candidate.candidate;
1842
+ const ipMatch = candidate.match(/([0-9]{1,3}\.){3}[0-9]{1,3}/);
1843
+ if (ipMatch) {
1844
+ const ip = ipMatch[0];
1845
+ if (this.isLocalIP(ip)) {
1846
+ if (!localIPs.includes(ip)) {
1847
+ localIPs.push(ip);
1848
+ }
1849
+ } else {
1850
+ publicIP = ip;
1851
+ }
1852
+ }
1853
+ }
1854
+ };
1855
+ pc.createDataChannel("");
1856
+ const offer = await pc.createOffer();
1857
+ await pc.setLocalDescription(offer);
1858
+ await new Promise((resolve) => {
1859
+ const timeout = setTimeout(() => {
1860
+ resolve();
1861
+ }, 2e3);
1862
+ pc.onicegatheringstatechange = () => {
1863
+ if (pc.iceGatheringState === "complete") {
1864
+ clearTimeout(timeout);
1865
+ resolve();
1866
+ }
1867
+ };
1868
+ if (pc.iceGatheringState === "complete") {
1869
+ clearTimeout(timeout);
1870
+ resolve();
1871
+ }
1872
+ });
1873
+ pc.close();
1874
+ const fingerprint = {
1875
+ localIPs: localIPs.sort(),
1876
+ publicIP,
1877
+ duration: 0
1878
+ };
1879
+ fingerprint.duration = Math.round((performance.now() - startTime) * 100) / 100;
1880
+ this.cachedFingerprint = fingerprint;
1881
+ return fingerprint;
1882
+ } catch (error) {
1883
+ return null;
1884
+ }
1885
+ }
1886
+ /**
1887
+ * Check if an IP is a local/private IP
1888
+ */
1889
+ isLocalIP(ip) {
1890
+ if (ip === "127.0.0.1" || ip === "::1") {
1891
+ return true;
1892
+ }
1893
+ const parts = ip.split(".");
1894
+ if (parts.length === 4) {
1895
+ const first = parseInt(parts[0], 10);
1896
+ const second = parseInt(parts[1], 10);
1897
+ if (first === 10) {
1898
+ return true;
1899
+ }
1900
+ if (first === 172 && second >= 16 && second <= 31) {
1901
+ return true;
1902
+ }
1903
+ if (first === 192 && second === 168) {
1904
+ return true;
1905
+ }
1906
+ if (first === 169 && second === 254) {
1907
+ return true;
1908
+ }
1909
+ }
1910
+ return false;
1911
+ }
1912
+ /**
1913
+ * Clear cached fingerprint
1914
+ */
1915
+ clearCache() {
1916
+ this.cachedFingerprint = null;
1917
+ }
1918
+ };
1919
+
1920
+ // ../fraud-sdk/src/collectors/visitor-id-generator.ts
1921
+ var VisitorIDGenerator = class {
1922
+ /**
1923
+ * Generate a stable visitor ID from fingerprint components
1924
+ * Uses SHA-256-like hashing for consistency
1925
+ */
1926
+ generate(components) {
1927
+ const identifiers = [];
1928
+ if (components.canvas?.value) {
1929
+ identifiers.push(`canvas:${components.canvas.value}`);
1930
+ }
1931
+ if (components.webgl) {
1932
+ if (components.webgl.vendor) {
1933
+ identifiers.push(`webgl_vendor:${components.webgl.vendor}`);
1934
+ }
1935
+ if (components.webgl.renderer) {
1936
+ identifiers.push(`webgl_renderer:${components.webgl.renderer}`);
1937
+ }
1938
+ if (components.webgl.extensions.length > 0) {
1939
+ identifiers.push(`webgl_ext:${components.webgl.extensions.slice(0, 10).join(",")}`);
1940
+ }
1941
+ }
1942
+ if (components.audio?.value) {
1943
+ identifiers.push(`audio:${components.audio.value}`);
1944
+ }
1945
+ if (components.screen) {
1946
+ identifiers.push(`screen:${components.screen.width}x${components.screen.height}x${components.screen.pixelRatio}`);
1947
+ identifiers.push(`color:${components.screen.colorDepth}`);
1948
+ }
1949
+ if (components.timezone) {
1950
+ identifiers.push(`tz:${components.timezone.timezone}`);
1951
+ identifiers.push(`locale:${components.timezone.locale}`);
1952
+ }
1953
+ if (components.cpu) {
1954
+ identifiers.push(`cpu:${components.cpu.hardwareConcurrency}`);
1955
+ if (components.cpu.deviceMemory) {
1956
+ identifiers.push(`memory:${components.cpu.deviceMemory}`);
1957
+ }
1958
+ }
1959
+ if (components.touch) {
1960
+ identifiers.push(`touch:${components.touch.maxTouchPoints}`);
1961
+ }
1962
+ identifiers.push(`ua:${navigator.userAgent}`);
1963
+ identifiers.push(`lang:${navigator.language}`);
1964
+ identifiers.push(`platform:${navigator.platform}`);
1965
+ if (components.fonts && components.fonts.availableFonts.length > 0) {
1966
+ identifiers.push(`fonts:${components.fonts.availableFonts.slice(0, 20).join(",")}`);
1967
+ }
1968
+ const combined = identifiers.join("|");
1969
+ return this.sha256Hash(combined);
1970
+ }
1971
+ /**
1972
+ * Calculate confidence score based on component availability
1973
+ * More components = higher confidence
1974
+ */
1975
+ calculateConfidence(components) {
1976
+ let score = 0;
1977
+ let maxScore = 0;
1978
+ const weights = {
1979
+ canvas: 20,
1980
+ webgl: 20,
1981
+ audio: 15,
1982
+ screen: 10,
1983
+ timezone: 10,
1984
+ cpu: 10,
1985
+ fonts: 10,
1986
+ touch: 5,
1987
+ storage: 5,
1988
+ permissions: 5,
1989
+ webrtc: 5,
1990
+ mediaDevices: 5,
1991
+ battery: 3,
1992
+ plugins: 3
1993
+ };
1994
+ Object.keys(weights).forEach((key) => {
1995
+ maxScore += weights[key];
1996
+ if (components[key]) {
1997
+ score += weights[key];
1998
+ }
1999
+ });
2000
+ return maxScore > 0 ? Math.min(score / maxScore, 1) : 0;
2001
+ }
2002
+ /**
2003
+ * SHA-256-like hash function
2004
+ * Produces a 64-character hex string (matching SHA-256 format)
2005
+ */
2006
+ sha256Hash(input) {
2007
+ if (typeof crypto !== "undefined" && crypto.subtle) {
2008
+ return this.sha256LikeHash(input);
2009
+ }
2010
+ return this.sha256LikeHash(input);
2011
+ }
2012
+ /**
2013
+ * SHA-256-like hash (synchronous, deterministic)
2014
+ * Produces 64-character hex string
2015
+ */
2016
+ sha256LikeHash(str) {
2017
+ const hashes = [];
2018
+ let h1 = 5381;
2019
+ for (let i = 0; i < str.length; i++) {
2020
+ h1 = (h1 << 5) + h1 + str.charCodeAt(i);
2021
+ h1 = h1 & 4294967295;
2022
+ }
2023
+ hashes.push(h1);
2024
+ let h2 = 0;
2025
+ for (let i = str.length - 1; i >= 0; i--) {
2026
+ h2 = (h2 << 7) - h2 + str.charCodeAt(i);
2027
+ h2 = h2 & 4294967295;
2028
+ }
2029
+ hashes.push(h2);
2030
+ let h3 = 0;
2031
+ for (let i = 0; i < str.length; i++) {
2032
+ h3 = h3 ^ str.charCodeAt(i) << i % 4 * 8;
2033
+ h3 = h3 & 4294967295;
2034
+ }
2035
+ hashes.push(h3);
2036
+ let h4 = 0;
2037
+ for (let i = 0; i < str.length; i++) {
2038
+ h4 = h4 * 31 + str.charCodeAt(i) & 4294967295;
2039
+ }
2040
+ hashes.push(h4);
2041
+ let h5 = 0;
2042
+ for (let i = 0; i < str.length; i++) {
2043
+ const rotated = (str.charCodeAt(i) << i % 16 | str.charCodeAt(i) >>> 32 - i % 16) & 4294967295;
2044
+ h5 = h5 + rotated & 4294967295;
2045
+ }
2046
+ hashes.push(h5);
2047
+ let h6 = 2166136261;
2048
+ for (let i = 0; i < str.length; i++) {
2049
+ h6 = (h6 ^ str.charCodeAt(i)) * 16777619;
2050
+ h6 = h6 & 4294967295;
2051
+ }
2052
+ hashes.push(h6);
2053
+ let h7 = 0;
2054
+ for (let i = 0; i < str.length; i++) {
2055
+ h7 = h7 + str.charCodeAt(i) * (i + 1) & 4294967295;
2056
+ }
2057
+ hashes.push(h7);
2058
+ let h8 = 0;
2059
+ for (let i = 0; i < str.length; i += 2) {
2060
+ const combined2 = str.charCodeAt(i) + (str.charCodeAt(i + 1) || 0) * 256;
2061
+ h8 = (h8 << 3) - h8 + combined2;
2062
+ h8 = h8 & 4294967295;
2063
+ }
2064
+ hashes.push(h8);
2065
+ const hexParts = hashes.map((h) => Math.abs(h).toString(16).padStart(8, "0"));
2066
+ const combined = hexParts.join("");
2067
+ return combined.substring(0, 64).padEnd(64, "0");
2068
+ }
2069
+ };
2070
+
2071
+ // ../fraud-sdk/src/collectors/fingerprint-manager.ts
2072
+ var FingerprintManager = class {
2073
+ constructor() {
2074
+ this.cachedData = null;
2075
+ this.collectionStartTime = 0;
2076
+ this.collectors = {
2077
+ canvas: new CanvasFingerprintCollector(),
2078
+ webgl: new WebGLFingerprintCollector(),
2079
+ audio: new AudioFingerprintCollector(),
2080
+ fonts: new FontDetectionCollector(),
2081
+ mediaDevices: new MediaDevicesCollector(),
2082
+ screen: new ScreenDisplayCollector(),
2083
+ timezone: new TimezoneLocaleCollector(),
2084
+ plugins: new PluginMimeCollector(),
2085
+ battery: new BatteryAPICollector(),
2086
+ storage: new StorageDatabaseCollector(),
2087
+ cpu: new CPUMemoryCollector(),
2088
+ touch: new TouchPointerCollector(),
2089
+ permissions: new PermissionsAPICollector(),
2090
+ webrtc: new WebRTCIPCollector()
2091
+ };
2092
+ this.visitorIDGenerator = new VisitorIDGenerator();
2093
+ }
2094
+ /**
2095
+ * Collect all fingerprint data
2096
+ * This is the main method that replaces FingerprintJS.get()
2097
+ */
2098
+ async collect() {
2099
+ if (this.cachedData) {
2100
+ return this.cachedData;
2101
+ }
2102
+ this.collectionStartTime = performance.now();
2103
+ const components = {};
2104
+ try {
2105
+ components.canvas = this.collectors.canvas.collect() || void 0;
2106
+ } catch (e) {
2107
+ console.warn("[Keverd SDK] Canvas collection failed:", e);
2108
+ }
2109
+ try {
2110
+ components.webgl = this.collectors.webgl.collect() || void 0;
2111
+ } catch (e) {
2112
+ console.warn("[Keverd SDK] WebGL collection failed:", e);
2113
+ }
2114
+ try {
2115
+ components.fonts = this.collectors.fonts.collect() || void 0;
2116
+ } catch (e) {
2117
+ console.warn("[Keverd SDK] Font detection failed:", e);
2118
+ }
2119
+ try {
2120
+ components.screen = this.collectors.screen.collect();
2121
+ } catch (e) {
2122
+ console.warn("[Keverd SDK] Screen collection failed:", e);
2123
+ }
2124
+ try {
2125
+ components.timezone = this.collectors.timezone.collect();
2126
+ } catch (e) {
2127
+ console.warn("[Keverd SDK] Timezone collection failed:", e);
2128
+ }
2129
+ try {
2130
+ components.plugins = this.collectors.plugins.collect() || void 0;
2131
+ } catch (e) {
2132
+ console.warn("[Keverd SDK] Plugin collection failed:", e);
2133
+ }
2134
+ try {
2135
+ components.cpu = this.collectors.cpu.collect();
2136
+ } catch (e) {
2137
+ console.warn("[Keverd SDK] CPU collection failed:", e);
2138
+ }
2139
+ try {
2140
+ components.touch = this.collectors.touch.collect();
2141
+ } catch (e) {
2142
+ console.warn("[Keverd SDK] Touch collection failed:", e);
2143
+ }
2144
+ try {
2145
+ components.audio = await this.collectors.audio.collect() || void 0;
2146
+ } catch (e) {
2147
+ console.warn("[Keverd SDK] Audio collection failed:", e);
2148
+ }
2149
+ try {
2150
+ components.mediaDevices = await this.collectors.mediaDevices.collect() || void 0;
2151
+ } catch (e) {
2152
+ console.warn("[Keverd SDK] Media devices collection failed:", e);
2153
+ }
2154
+ try {
2155
+ components.battery = await this.collectors.battery.collect() || void 0;
2156
+ } catch (e) {
2157
+ console.warn("[Keverd SDK] Battery collection failed:", e);
2158
+ }
2159
+ try {
2160
+ components.storage = await this.collectors.storage.collect();
2161
+ } catch (e) {
2162
+ console.warn("[Keverd SDK] Storage collection failed:", e);
2163
+ }
2164
+ try {
2165
+ components.permissions = await this.collectors.permissions.collect() || void 0;
2166
+ } catch (e) {
2167
+ console.warn("[Keverd SDK] Permissions collection failed:", e);
2168
+ }
2169
+ try {
2170
+ components.webrtc = await this.collectors.webrtc.collect() || void 0;
2171
+ } catch (e) {
2172
+ console.warn("[Keverd SDK] WebRTC collection failed:", e);
2173
+ }
2174
+ const visitorId = this.visitorIDGenerator.generate(components);
2175
+ const confidence = this.visitorIDGenerator.calculateConfidence(components);
2176
+ this.cachedData = {
2177
+ visitorId,
2178
+ confidence: Math.round(confidence * 1e3) / 1e3,
2179
+ // Round to 3 decimal places
2180
+ components,
2181
+ version: "1.0.0",
2182
+ // SDK version
2183
+ timestamp: Date.now()
2184
+ };
2185
+ return this.cachedData;
2186
+ }
2187
+ /**
2188
+ * Get visitor ID only (faster, uses cached data if available)
2189
+ */
2190
+ async getVisitorId() {
2191
+ const data = await this.collect();
2192
+ return data.visitorId;
2193
+ }
2194
+ /**
2195
+ * Clear all cached data
2196
+ */
2197
+ clearCache() {
2198
+ this.cachedData = null;
2199
+ Object.values(this.collectors).forEach((collector) => {
2200
+ if (collector && typeof collector.clearCache === "function") {
2201
+ collector.clearCache();
2202
+ }
2203
+ });
2204
+ }
2205
+ /**
2206
+ * Get collection statistics
2207
+ */
2208
+ getStats() {
2209
+ if (!this.cachedData) {
2210
+ return {
2211
+ componentsCollected: 0,
2212
+ totalComponents: 13,
2213
+ collectionTime: 0
2214
+ };
2215
+ }
2216
+ const componentsCollected = Object.keys(this.cachedData.components).length;
2217
+ const collectionTime = performance.now() - this.collectionStartTime;
2218
+ return {
2219
+ componentsCollected,
2220
+ totalComponents: 13,
2221
+ collectionTime: Math.round(collectionTime * 100) / 100
2222
+ };
2223
+ }
2224
+ };
2225
+
2226
+ // ../fraud-sdk/src/collectors/privacy-signals.ts
2227
+ var PrivacySignalCollector = class {
2228
+ constructor() {
2229
+ this.cachedSignals = null;
2230
+ }
2231
+ /**
2232
+ * Collect all privacy signals
2233
+ */
2234
+ collect() {
2235
+ if (this.cachedSignals) {
2236
+ return this.cachedSignals;
2237
+ }
2238
+ const signals = {
2239
+ isIncognito: this.detectIncognito(),
2240
+ isVPN: false,
2241
+ // VPN detection requires backend IP analysis
2242
+ isAutomated: this.detectAutomation(),
2243
+ hasAdBlocker: this.detectAdBlocker()
2244
+ };
2245
+ this.cachedSignals = signals;
2246
+ return signals;
2247
+ }
2248
+ /**
2249
+ * Detect incognito/private mode
2250
+ * Uses multiple detection methods for accuracy
2251
+ */
2252
+ detectIncognito() {
2253
+ try {
2254
+ if ("storage" in navigator && "estimate" in navigator.storage) {
2255
+ try {
2256
+ navigator.storage.estimate().then(() => {
2257
+ }).catch(() => {
2258
+ });
2259
+ } catch {
2260
+ return true;
2261
+ }
2262
+ }
2263
+ if ("storage" in navigator && "estimate" in navigator.storage) {
2264
+ }
2265
+ if ("getBattery" in navigator) {
2266
+ }
2267
+ if (window.chrome && window.chrome.runtime && !window.chrome.runtime.onConnect) {
2268
+ return true;
2269
+ }
2270
+ try {
2271
+ const testKey = "__incognito_test__";
2272
+ localStorage.setItem(testKey, "test");
2273
+ localStorage.removeItem(testKey);
2274
+ } catch {
2275
+ return true;
2276
+ }
2277
+ return false;
2278
+ } catch (error) {
2279
+ return false;
2280
+ }
2281
+ }
2282
+ /**
2283
+ * Detect automation/bot frameworks
2284
+ */
2285
+ detectAutomation() {
2286
+ try {
2287
+ if (navigator.webdriver === true) {
2288
+ return true;
2289
+ }
2290
+ if (window.chrome && window.chrome.runtime && window.chrome.runtime.onConnect) {
2291
+ const hasDevTools = window.chrome.runtime.onConnect.hasListeners();
2292
+ if (hasDevTools) {
2293
+ return true;
2294
+ }
2295
+ }
2296
+ if (!window.chrome && !window.safari) {
2297
+ if (!window.Notification || !window.DeviceMotionEvent) {
2298
+ return true;
2299
+ }
2300
+ }
2301
+ try {
2302
+ const canvas = document.createElement("canvas");
2303
+ const ctx = canvas.getContext("2d");
2304
+ if (ctx) {
2305
+ ctx.textBaseline = "top";
2306
+ ctx.font = "14px Arial";
2307
+ ctx.fillText("Automation test", 2, 2);
2308
+ const fingerprint = canvas.toDataURL();
2309
+ if (fingerprint.length < 100) {
2310
+ return true;
2311
+ }
2312
+ }
2313
+ } catch {
2314
+ }
2315
+ if (window.__nightmare || window.__phantomas || window.Buffer) {
2316
+ return true;
2317
+ }
2318
+ return false;
2319
+ } catch (error) {
2320
+ return false;
2321
+ }
2322
+ }
2323
+ /**
2324
+ * Detect ad blocker
2325
+ */
2326
+ detectAdBlocker() {
2327
+ try {
2328
+ const testDiv = document.createElement("div");
2329
+ testDiv.innerHTML = "&nbsp;";
2330
+ testDiv.className = "adsbox";
2331
+ testDiv.style.position = "absolute";
2332
+ testDiv.style.left = "-9999px";
2333
+ document.body.appendChild(testDiv);
2334
+ setTimeout(() => {
2335
+ const isBlocked = testDiv.offsetHeight === 0 || testDiv.offsetWidth === 0;
2336
+ document.body.removeChild(testDiv);
2337
+ return isBlocked;
2338
+ }, 100);
2339
+ const scripts = Array.from(document.getElementsByTagName("script"));
2340
+ const blockedCount = scripts.filter((script) => {
2341
+ return script.src && script.src.includes("ads") && !script.textContent;
2342
+ }).length;
2343
+ if (blockedCount > 0) {
2344
+ return true;
2345
+ }
2346
+ if (window.uBlock || window.adblock || window.BlockAdBlock) {
2347
+ return true;
2348
+ }
2349
+ return false;
2350
+ } catch (error) {
2351
+ return false;
2352
+ }
2353
+ }
2354
+ /**
2355
+ * Clear cached signals (useful for testing)
2356
+ */
2357
+ clearCache() {
2358
+ this.cachedSignals = null;
2359
+ }
2360
+ };
2361
+
2362
+ // ../fraud-sdk/src/collectors/browser-capabilities.ts
2363
+ var BrowserCapabilityCollector = class {
2364
+ constructor() {
2365
+ this.cachedCapabilities = null;
2366
+ }
2367
+ /**
2368
+ * Collect all browser capabilities
2369
+ */
2370
+ collect() {
2371
+ if (this.cachedCapabilities) {
2372
+ return this.cachedCapabilities;
2373
+ }
2374
+ const capabilities = {
2375
+ hasWebGL: this.checkWebGL(),
2376
+ hasCanvas: this.checkCanvas(),
2377
+ hasWebRTC: this.checkWebRTC(),
2378
+ hasServiceWorker: this.checkServiceWorker(),
2379
+ hasIndexedDB: this.checkIndexedDB(),
2380
+ hasLocalStorage: this.checkLocalStorage(),
2381
+ hasSessionStorage: this.checkSessionStorage(),
2382
+ cookieEnabled: navigator.cookieEnabled,
2383
+ doNotTrack: navigator.doNotTrack === "1" || navigator.doNotTrack === "yes"
2384
+ };
2385
+ this.cachedCapabilities = capabilities;
2386
+ return capabilities;
2387
+ }
2388
+ checkWebGL() {
2389
+ try {
2390
+ const canvas = document.createElement("canvas");
2391
+ return !!(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"));
2392
+ } catch {
2393
+ return false;
2394
+ }
2395
+ }
2396
+ checkCanvas() {
2397
+ try {
2398
+ const canvas = document.createElement("canvas");
2399
+ return !!canvas.getContext("2d");
2400
+ } catch {
2401
+ return false;
2402
+ }
2403
+ }
2404
+ checkWebRTC() {
2405
+ return !!((window.RTCPeerConnection || // @ts-ignore
2406
+ window.webkitRTCPeerConnection || // @ts-ignore
2407
+ window.mozRTCPeerConnection) && navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
2408
+ }
2409
+ checkServiceWorker() {
2410
+ return "serviceWorker" in navigator;
2411
+ }
2412
+ checkIndexedDB() {
2413
+ return "indexedDB" in window;
2414
+ }
2415
+ checkLocalStorage() {
2416
+ try {
2417
+ const test = "__localStorage_test__";
2418
+ localStorage.setItem(test, test);
2419
+ localStorage.removeItem(test);
2420
+ return true;
2421
+ } catch {
2422
+ return false;
2423
+ }
2424
+ }
2425
+ checkSessionStorage() {
2426
+ try {
2427
+ const test = "__sessionStorage_test__";
2428
+ sessionStorage.setItem(test, test);
2429
+ sessionStorage.removeItem(test);
2430
+ return true;
2431
+ } catch {
2432
+ return false;
2433
+ }
2434
+ }
2435
+ /**
2436
+ * Clear cached capabilities (useful for testing)
2437
+ */
2438
+ clearCache() {
2439
+ this.cachedCapabilities = null;
2440
+ }
2441
+ };
2442
+
2443
+ // ../fraud-sdk/src/collectors/hardware-signals.ts
2444
+ var HardwareSignalCollector = class {
2445
+ constructor() {
2446
+ this.cachedSignals = null;
2447
+ }
2448
+ /**
2449
+ * Collect all hardware signals
2450
+ */
2451
+ collect() {
2452
+ if (this.cachedSignals) {
2453
+ return this.cachedSignals;
2454
+ }
2455
+ const signals = {
2456
+ hardwareConcurrency: navigator.hardwareConcurrency || 0,
2457
+ deviceMemory: this.getDeviceMemory(),
2458
+ maxTouchPoints: navigator.maxTouchPoints || 0,
2459
+ connectionType: this.getConnectionType(),
2460
+ effectiveType: this.getEffectiveType(),
2461
+ downlink: this.getDownlink(),
2462
+ rtt: this.getRTT(),
2463
+ saveData: this.getSaveData()
2464
+ };
2465
+ this.cachedSignals = signals;
2466
+ return signals;
2467
+ }
2468
+ getDeviceMemory() {
2469
+ if ("deviceMemory" in navigator && navigator.deviceMemory) {
2470
+ return navigator.deviceMemory;
2471
+ }
2472
+ return null;
2473
+ }
2474
+ getConnectionType() {
2475
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
2476
+ if (connection && connection.type) {
2477
+ return connection.type;
2478
+ }
2479
+ return null;
2480
+ }
2481
+ getEffectiveType() {
2482
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
2483
+ if (connection && connection.effectiveType) {
2484
+ return connection.effectiveType;
2485
+ }
2486
+ return null;
2487
+ }
2488
+ getDownlink() {
2489
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
2490
+ if (connection && connection.downlink) {
2491
+ return connection.downlink;
2492
+ }
2493
+ return null;
2494
+ }
2495
+ getRTT() {
2496
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
2497
+ if (connection && connection.rtt) {
2498
+ return connection.rtt;
2499
+ }
2500
+ return null;
2501
+ }
2502
+ getSaveData() {
2503
+ const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
2504
+ if (connection && connection.saveData !== void 0) {
2505
+ return connection.saveData;
2506
+ }
2507
+ return null;
2508
+ }
2509
+ /**
2510
+ * Clear cached signals (useful for testing)
2511
+ */
2512
+ clearCache() {
2513
+ this.cachedSignals = null;
2514
+ }
2515
+ };
2516
+
2517
+ // ../fraud-sdk/src/collectors/form-interactions.ts
2518
+ var FormInteractionCollector = class {
14
2519
  constructor() {
2520
+ this.focusCount = 0;
2521
+ this.blurCount = 0;
2522
+ this.copyCount = 0;
2523
+ this.pasteCount = 0;
2524
+ this.cutCount = 0;
2525
+ this.fieldFocusOrder = [];
2526
+ this.fieldFocusTimes = /* @__PURE__ */ new Map();
2527
+ this.backspaceCount = 0;
2528
+ this.deleteCount = 0;
2529
+ this.autofillDetected = false;
2530
+ this.autocompleteDetected = false;
2531
+ this.isActive = false;
2532
+ this.focusHandler = (event) => this.handleFocus(event);
2533
+ this.blurHandler = (event) => this.handleBlur(event);
2534
+ this.copyHandler = (event) => this.handleCopy(event);
2535
+ this.pasteHandler = (event) => this.handlePaste(event);
2536
+ this.cutHandler = (event) => this.handleCut(event);
2537
+ this.keydownHandler = (event) => this.handleKeyDown(event);
2538
+ this.inputHandler = (event) => this.handleInput(event);
2539
+ }
2540
+ /**
2541
+ * Start collecting form interactions
2542
+ */
2543
+ start() {
2544
+ if (this.isActive || typeof document === "undefined") return;
2545
+ document.addEventListener("focus", this.focusHandler, true);
2546
+ document.addEventListener("blur", this.blurHandler, true);
2547
+ document.addEventListener("copy", this.copyHandler, true);
2548
+ document.addEventListener("paste", this.pasteHandler, true);
2549
+ document.addEventListener("cut", this.cutHandler, true);
2550
+ document.addEventListener("keydown", this.keydownHandler, true);
2551
+ document.addEventListener("input", this.inputHandler, true);
2552
+ this.isActive = true;
2553
+ }
2554
+ /**
2555
+ * Stop collecting form interactions
2556
+ */
2557
+ stop() {
2558
+ if (!this.isActive || typeof document === "undefined") return;
2559
+ document.removeEventListener("focus", this.focusHandler, true);
2560
+ document.removeEventListener("blur", this.blurHandler, true);
2561
+ document.removeEventListener("copy", this.copyHandler, true);
2562
+ document.removeEventListener("paste", this.pasteHandler, true);
2563
+ document.removeEventListener("cut", this.cutHandler, true);
2564
+ document.removeEventListener("keydown", this.keydownHandler, true);
2565
+ document.removeEventListener("input", this.inputHandler, true);
2566
+ this.isActive = false;
2567
+ }
2568
+ /**
2569
+ * Get collected form interaction data
2570
+ */
2571
+ getData() {
2572
+ const timePerField = {};
2573
+ this.fieldFocusTimes.forEach((time, fieldId) => {
2574
+ timePerField[fieldId] = time;
2575
+ });
2576
+ return {
2577
+ focusCount: this.focusCount,
2578
+ blurCount: this.blurCount,
2579
+ copyCount: this.copyCount,
2580
+ pasteCount: this.pasteCount,
2581
+ cutCount: this.cutCount,
2582
+ autofillDetected: this.autofillDetected,
2583
+ autocompleteDetected: this.autocompleteDetected,
2584
+ fieldFocusOrder: [...this.fieldFocusOrder],
2585
+ timePerField,
2586
+ backspaceCount: this.backspaceCount,
2587
+ deleteCount: this.deleteCount
2588
+ };
2589
+ }
2590
+ /**
2591
+ * Reset all collected data
2592
+ */
2593
+ reset() {
2594
+ this.focusCount = 0;
2595
+ this.blurCount = 0;
2596
+ this.copyCount = 0;
2597
+ this.pasteCount = 0;
2598
+ this.cutCount = 0;
2599
+ this.fieldFocusOrder = [];
2600
+ this.fieldFocusTimes.clear();
2601
+ this.backspaceCount = 0;
2602
+ this.deleteCount = 0;
2603
+ this.autofillDetected = false;
2604
+ this.autocompleteDetected = false;
2605
+ }
2606
+ handleFocus(event) {
2607
+ const target = event.target;
2608
+ if (!this.isFormField(target)) return;
2609
+ this.focusCount++;
2610
+ const fieldId = this.getFieldId(target);
2611
+ if (fieldId && !this.fieldFocusOrder.includes(fieldId)) {
2612
+ this.fieldFocusOrder.push(fieldId);
2613
+ }
2614
+ this.fieldFocusTimes.set(fieldId || "unknown", Date.now());
2615
+ }
2616
+ handleBlur(event) {
2617
+ const target = event.target;
2618
+ if (!this.isFormField(target)) return;
2619
+ this.blurCount++;
2620
+ const fieldId = this.getFieldId(target);
2621
+ if (fieldId) {
2622
+ const focusTime = this.fieldFocusTimes.get(fieldId) || Date.now();
2623
+ const timeSpent = Date.now() - focusTime;
2624
+ this.fieldFocusTimes.set(fieldId, timeSpent);
2625
+ }
2626
+ }
2627
+ handleCopy(event) {
2628
+ const target = event.target;
2629
+ if (this.isFormField(target)) {
2630
+ this.copyCount++;
2631
+ }
2632
+ }
2633
+ handlePaste(event) {
2634
+ const target = event.target;
2635
+ if (this.isFormField(target)) {
2636
+ this.pasteCount++;
2637
+ }
2638
+ }
2639
+ handleCut(event) {
2640
+ const target = event.target;
2641
+ if (this.isFormField(target)) {
2642
+ this.cutCount++;
2643
+ }
2644
+ }
2645
+ handleKeyDown(event) {
2646
+ const target = event.target;
2647
+ if (!this.isFormField(target)) return;
2648
+ if (event.key === "Backspace") {
2649
+ this.backspaceCount++;
2650
+ } else if (event.key === "Delete") {
2651
+ this.deleteCount++;
2652
+ }
2653
+ }
2654
+ handleInput(event) {
2655
+ const target = event.target;
2656
+ if (!this.isFormField(target)) return;
2657
+ const value = target.value;
2658
+ if (value && target.type === "password") {
2659
+ this.autofillDetected = true;
2660
+ }
2661
+ if (target.hasAttribute("autocomplete") && target.getAttribute("autocomplete") !== "off") {
2662
+ this.autocompleteDetected = true;
2663
+ }
2664
+ }
2665
+ isFormField(element) {
2666
+ if (!element) return false;
2667
+ const tagName = element.tagName.toLowerCase();
2668
+ return tagName === "input" || tagName === "textarea" || tagName === "select" || element.isContentEditable;
2669
+ }
2670
+ getFieldId(element) {
2671
+ if (element.id) return element.id;
2672
+ if ("name" in element && element.name) {
2673
+ return element.name;
2674
+ }
2675
+ if (element.getAttribute("data-field-id")) {
2676
+ return element.getAttribute("data-field-id");
2677
+ }
2678
+ return null;
2679
+ }
2680
+ };
2681
+
2682
+ // ../fraud-sdk/src/collectors/page-interactions.ts
2683
+ var PageInteractionCollector = class {
2684
+ constructor() {
2685
+ this.pageLoadTime = 0;
2686
+ this.timeToFirstInteraction = 0;
2687
+ this.startTime = Date.now();
2688
+ this.firstInteractionTime = null;
2689
+ this.maxScrollDepth = 0;
2690
+ this.clickCount = 0;
2691
+ this.linkClickCount = 0;
2692
+ this.imageViewCount = 0;
2693
+ this.videoPlayCount = 0;
2694
+ this.isActive = false;
2695
+ this.clickHandler = (event) => this.handleClick(event);
2696
+ this.scrollHandler = () => this.handleScroll();
2697
+ this.loadHandler = () => this.handleLoad();
2698
+ this.imageLoadHandler = () => this.handleImageLoad();
2699
+ this.videoPlayHandler = () => this.handleVideoPlay();
2700
+ this.startTime = performance.now();
2701
+ }
2702
+ /**
2703
+ * Start collecting page interactions
2704
+ */
2705
+ start() {
2706
+ if (this.isActive || typeof document === "undefined") return;
2707
+ if (document.readyState === "complete") {
2708
+ this.pageLoadTime = performance.now() - this.startTime;
2709
+ } else {
2710
+ window.addEventListener("load", this.loadHandler);
2711
+ }
2712
+ document.addEventListener("click", this.clickHandler, true);
2713
+ window.addEventListener("scroll", this.scrollHandler, { passive: true });
2714
+ document.addEventListener("load", this.imageLoadHandler, true);
2715
+ document.addEventListener("play", this.videoPlayHandler, true);
2716
+ this.isActive = true;
2717
+ }
2718
+ /**
2719
+ * Stop collecting page interactions
2720
+ */
2721
+ stop() {
2722
+ if (!this.isActive || typeof document === "undefined") return;
2723
+ window.removeEventListener("load", this.loadHandler);
2724
+ document.removeEventListener("click", this.clickHandler, true);
2725
+ window.removeEventListener("scroll", this.scrollHandler);
2726
+ document.removeEventListener("load", this.imageLoadHandler, true);
2727
+ document.removeEventListener("play", this.videoPlayHandler, true);
2728
+ this.isActive = false;
2729
+ }
2730
+ /**
2731
+ * Get collected page interaction data
2732
+ */
2733
+ getData() {
2734
+ const timeOnPage = Date.now() - this.startTime;
2735
+ const scrollDepth = this.calculateScrollDepth();
2736
+ return {
2737
+ pageLoadTime: this.pageLoadTime,
2738
+ timeToFirstInteraction: this.firstInteractionTime ? this.firstInteractionTime - this.startTime : 0,
2739
+ timeOnPage,
2740
+ scrollDepth,
2741
+ clickCount: this.clickCount,
2742
+ linkClickCount: this.linkClickCount,
2743
+ imageViewCount: this.imageViewCount,
2744
+ videoPlayCount: this.videoPlayCount
2745
+ };
2746
+ }
2747
+ /**
2748
+ * Reset all collected data
2749
+ */
2750
+ reset() {
2751
+ this.pageLoadTime = 0;
2752
+ this.timeToFirstInteraction = 0;
2753
+ this.startTime = Date.now();
2754
+ this.firstInteractionTime = null;
2755
+ this.maxScrollDepth = 0;
2756
+ this.clickCount = 0;
2757
+ this.linkClickCount = 0;
2758
+ this.imageViewCount = 0;
2759
+ this.videoPlayCount = 0;
2760
+ }
2761
+ handleLoad() {
2762
+ this.pageLoadTime = performance.now() - this.startTime;
2763
+ }
2764
+ handleClick(event) {
2765
+ if (!this.firstInteractionTime) {
2766
+ this.firstInteractionTime = performance.now();
2767
+ }
2768
+ this.clickCount++;
2769
+ const target = event.target;
2770
+ if (target.tagName.toLowerCase() === "a" || target.closest("a")) {
2771
+ this.linkClickCount++;
2772
+ }
2773
+ }
2774
+ handleScroll() {
2775
+ const scrollDepth = this.calculateScrollDepth();
2776
+ this.maxScrollDepth = Math.max(this.maxScrollDepth, scrollDepth);
2777
+ }
2778
+ handleImageLoad() {
2779
+ this.imageViewCount++;
2780
+ }
2781
+ handleVideoPlay() {
2782
+ this.videoPlayCount++;
2783
+ }
2784
+ calculateScrollDepth() {
2785
+ if (typeof window === "undefined" || typeof document === "undefined") {
2786
+ return 0;
2787
+ }
2788
+ const windowHeight = window.innerHeight || document.documentElement.clientHeight;
2789
+ const documentHeight = Math.max(
2790
+ document.body.scrollHeight,
2791
+ document.body.offsetHeight,
2792
+ document.documentElement.clientHeight,
2793
+ document.documentElement.scrollHeight,
2794
+ document.documentElement.offsetHeight
2795
+ );
2796
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
2797
+ if (documentHeight === 0) return 0;
2798
+ const scrolled = scrollTop + windowHeight;
2799
+ return Math.min(scrolled / documentHeight, 1);
2800
+ }
2801
+ };
2802
+
2803
+ // ../fraud-sdk/src/core/sdk.ts
2804
+ var KeverdSDK = class {
2805
+ constructor() {
2806
+ this.config = null;
2807
+ this.isInitialized = false;
2808
+ this.sessionId = null;
2809
+ this.deviceCollector = new DeviceCollector();
2810
+ this.behavioralCollector = new BehavioralCollector();
2811
+ this.fingerprintManager = new FingerprintManager();
2812
+ this.privacySignalCollector = new PrivacySignalCollector();
2813
+ this.browserCapabilityCollector = new BrowserCapabilityCollector();
2814
+ this.hardwareSignalCollector = new HardwareSignalCollector();
2815
+ this.formInteractionCollector = new FormInteractionCollector();
2816
+ this.pageInteractionCollector = new PageInteractionCollector();
2817
+ }
2818
+ /**
2819
+ * Initialize the SDK with configuration
2820
+ */
2821
+ init(config) {
2822
+ if (this.isInitialized) {
2823
+ if (this.config?.debug) {
2824
+ console.warn("[Keverd SDK] Already initialized");
2825
+ }
2826
+ return;
2827
+ }
2828
+ if (typeof config === "string") {
2829
+ this.config = {
2830
+ apiKey: config,
2831
+ endpoint: "https://api.keverd.com",
2832
+ debug: false
2833
+ };
2834
+ } else {
2835
+ this.config = {
2836
+ endpoint: "https://api.keverd.com",
2837
+ debug: false,
2838
+ ...config
2839
+ };
2840
+ }
2841
+ this.behavioralCollector.start();
2842
+ this.formInteractionCollector.start();
2843
+ this.pageInteractionCollector.start();
2844
+ this.sessionId = this.generateSessionId();
2845
+ this.isInitialized = true;
2846
+ if (this.config.debug) {
2847
+ console.log("[Keverd SDK] Initialized successfully", {
2848
+ endpoint: this.config.endpoint
2849
+ });
2850
+ }
2851
+ }
2852
+ /**
2853
+ * Get visitor data (fingerprint and risk assessment)
2854
+ * This is the main method for getting risk scores
2855
+ */
2856
+ async getVisitorData() {
2857
+ if (!this.isInitialized || !this.config) {
2858
+ throw new Error("Keverd SDK not initialized. Call init() first.");
2859
+ }
2860
+ try {
2861
+ const deviceInfo = this.deviceCollector.collect();
2862
+ const behavioralData = this.behavioralCollector.getData();
2863
+ const fingerprintData = await this.fingerprintManager.collect();
2864
+ const fingerprintjsData = {
2865
+ visitorId: fingerprintData.visitorId,
2866
+ confidence: fingerprintData.confidence,
2867
+ components: fingerprintData.components
2868
+ };
2869
+ const privacySignals = this.privacySignalCollector.collect();
2870
+ const browserCapabilities = this.browserCapabilityCollector.collect();
2871
+ const hardwareSignals = this.hardwareSignalCollector.collect();
2872
+ const formInteractions = this.formInteractionCollector.getData();
2873
+ const mouseSignals = this.behavioralCollector.getMouseSignals();
2874
+ const keyboardSignals = this.behavioralCollector.getKeyboardSignals();
2875
+ const pageInteractions = this.pageInteractionCollector.getData();
2876
+ const networkSignals = {
2877
+ connectionType: hardwareSignals.connectionType || void 0,
2878
+ effectiveType: hardwareSignals.effectiveType || void 0,
2879
+ downlink: hardwareSignals.downlink || void 0,
2880
+ rtt: hardwareSignals.rtt || void 0,
2881
+ saveData: hardwareSignals.saveData || void 0
2882
+ };
2883
+ const sessionInfo = {
2884
+ sessionId: this.sessionId || void 0,
2885
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2886
+ };
2887
+ const request = {
2888
+ userId: this.config.userId,
2889
+ device: deviceInfo,
2890
+ session: sessionInfo,
2891
+ behavioral: behavioralData,
2892
+ fingerprintjs: fingerprintjsData || void 0,
2893
+ privacySignals,
2894
+ browserCapabilities,
2895
+ hardwareSignals,
2896
+ formInteractions,
2897
+ mouseSignals,
2898
+ keyboardSignals,
2899
+ pageInteractions,
2900
+ networkSignals,
2901
+ useCase: "general"
2902
+ // Note: sim field is NOT included for web SDKs
2903
+ };
2904
+ const response = await this.sendFingerprintRequest(request);
2905
+ return response;
2906
+ } catch (error) {
2907
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2908
+ if (this.config.debug) {
2909
+ console.error("[Keverd SDK] Error getting visitor data:", error);
2910
+ }
2911
+ throw new Error(`Failed to get visitor data: ${errorMessage}`);
2912
+ }
2913
+ }
2914
+ /**
2915
+ * Create transaction ID with risk assessment (legacy method)
2916
+ * For new implementations, use getVisitorData() instead
2917
+ */
2918
+ async createTransactionID(_metadata) {
2919
+ const response = await this.getVisitorData();
2920
+ return response.session_id;
2921
+ }
2922
+ /**
2923
+ * Verify user identity during login attempts
2924
+ */
2925
+ async verifyLogin(userId, metadata) {
2926
+ if (!this.isInitialized || !this.config) {
2927
+ throw new Error("Keverd SDK not initialized. Call init() first.");
2928
+ }
2929
+ const response = await this.sendVerifyRequest("login", { userId, metadata });
2930
+ return response;
2931
+ }
2932
+ /**
2933
+ * Verify user during checkout/payment
2934
+ */
2935
+ async verifyCheckout(amount, currency, metadata) {
2936
+ if (!this.isInitialized || !this.config) {
2937
+ throw new Error("Keverd SDK not initialized. Call init() first.");
2938
+ }
2939
+ const response = await this.sendVerifyRequest("checkout", { amount, currency, metadata });
2940
+ return response;
2941
+ }
2942
+ /**
2943
+ * Detect fake/bot registrations
2944
+ */
2945
+ async verifyRegistration(metadata) {
2946
+ if (!this.isInitialized || !this.config) {
2947
+ throw new Error("Keverd SDK not initialized. Call init() first.");
2948
+ }
2949
+ const response = await this.sendVerifyRequest("registration", { metadata });
2950
+ return response;
2951
+ }
2952
+ /**
2953
+ * Detect account takeover attempts during password reset
2954
+ */
2955
+ async verifyPasswordReset(email, metadata) {
2956
+ if (!this.isInitialized || !this.config) {
2957
+ throw new Error("Keverd SDK not initialized. Call init() first.");
2958
+ }
2959
+ const response = await this.sendVerifyRequest("password_reset", { email, metadata });
2960
+ return response;
2961
+ }
2962
+ /**
2963
+ * Verify sensitive account changes (email, phone, password)
2964
+ */
2965
+ async verifyAccountChange(changeType, metadata) {
2966
+ if (!this.isInitialized || !this.config) {
2967
+ throw new Error("Keverd SDK not initialized. Call init() first.");
2968
+ }
2969
+ const response = await this.sendVerifyRequest("account_change", { changeType, metadata });
2970
+ return response;
2971
+ }
2972
+ /**
2973
+ * Helper to send verify request to use-case-specific endpoint
2974
+ */
2975
+ async sendVerifyRequest(useCase, options) {
2976
+ if (!this.config) {
2977
+ throw new Error("SDK not initialized");
2978
+ }
2979
+ const deviceInfo = this.deviceCollector.collect();
2980
+ const behavioralData = this.behavioralCollector.getData();
2981
+ const fingerprintData = await this.fingerprintManager.collect();
2982
+ const fingerprintjsData = {
2983
+ visitorId: fingerprintData.visitorId,
2984
+ confidence: fingerprintData.confidence,
2985
+ components: fingerprintData.components
2986
+ };
2987
+ const privacySignals = this.privacySignalCollector.collect();
2988
+ const browserCapabilities = this.browserCapabilityCollector.collect();
2989
+ const hardwareSignals = this.hardwareSignalCollector.collect();
2990
+ const formInteractions = this.formInteractionCollector.getData();
2991
+ const mouseSignals = this.behavioralCollector.getMouseSignals();
2992
+ const keyboardSignals = this.behavioralCollector.getKeyboardSignals();
2993
+ const pageInteractions = this.pageInteractionCollector.getData();
2994
+ const networkSignals = {
2995
+ connectionType: hardwareSignals.connectionType || void 0,
2996
+ effectiveType: hardwareSignals.effectiveType || void 0,
2997
+ downlink: hardwareSignals.downlink || void 0,
2998
+ rtt: hardwareSignals.rtt || void 0,
2999
+ saveData: hardwareSignals.saveData || void 0
3000
+ };
3001
+ const sessionInfo = {
3002
+ sessionId: this.sessionId || void 0,
3003
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3004
+ };
3005
+ const request = {
3006
+ userId: options.userId || this.config.userId,
3007
+ device: deviceInfo,
3008
+ session: sessionInfo,
3009
+ behavioral: behavioralData,
3010
+ fingerprintjs: fingerprintjsData || void 0,
3011
+ privacySignals,
3012
+ browserCapabilities,
3013
+ hardwareSignals,
3014
+ formInteractions,
3015
+ mouseSignals,
3016
+ keyboardSignals,
3017
+ pageInteractions,
3018
+ networkSignals,
3019
+ useCase
3020
+ };
3021
+ if (options.metadata) {
3022
+ Object.assign(request, options.metadata);
3023
+ }
3024
+ if (options.amount !== void 0) {
3025
+ request.amount = options.amount;
3026
+ }
3027
+ if (options.currency) {
3028
+ request.currency = options.currency;
3029
+ }
3030
+ if (options.email) {
3031
+ request.email = options.email;
3032
+ }
3033
+ if (options.changeType) {
3034
+ request.changeType = options.changeType;
3035
+ }
3036
+ const endpoint = this.config.endpoint || "https://api.keverd.com";
3037
+ const url = `${endpoint}/fingerprint/verify/${useCase}`;
3038
+ const headers = {
3039
+ "Content-Type": "application/json",
3040
+ "X-SDK-Source": "javascript"
3041
+ };
3042
+ const apiKey = this.config.apiKey;
3043
+ if (apiKey) {
3044
+ headers["x-keverd-key"] = apiKey;
3045
+ headers["X-API-KEY"] = apiKey;
3046
+ headers["Authorization"] = `Bearer ${apiKey}`;
3047
+ }
3048
+ const response = await fetch(url, {
3049
+ method: "POST",
3050
+ headers,
3051
+ body: JSON.stringify(request)
3052
+ });
3053
+ if (!response.ok) {
3054
+ const errorText = await response.text().catch(() => "Unknown error");
3055
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
3056
+ }
3057
+ const data = await response.json();
3058
+ return data;
3059
+ }
3060
+ /**
3061
+ * Send fingerprint request to backend
3062
+ */
3063
+ async sendFingerprintRequest(request) {
3064
+ if (!this.config) {
3065
+ throw new Error("SDK not initialized");
3066
+ }
3067
+ const endpoint = this.config.endpoint || "https://api.keverd.com";
3068
+ const url = `${endpoint}/fingerprint/score`;
3069
+ const headers = {
3070
+ "Content-Type": "application/json",
3071
+ "X-SDK-Source": "javascript"
3072
+ // Identify SDK source for backend analytics
3073
+ };
3074
+ const apiKey = this.config.apiKey;
3075
+ if (apiKey) {
3076
+ headers["x-keverd-key"] = apiKey;
3077
+ headers["X-API-KEY"] = apiKey;
3078
+ headers["Authorization"] = `Bearer ${apiKey}`;
3079
+ }
3080
+ const response = await fetch(url, {
3081
+ method: "POST",
3082
+ headers,
3083
+ body: JSON.stringify(request)
3084
+ });
3085
+ if (!response.ok) {
3086
+ const errorText = await response.text().catch(() => "Unknown error");
3087
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
3088
+ }
3089
+ const data = await response.json();
3090
+ return data;
3091
+ }
3092
+ /**
3093
+ * Generate a unique session ID
3094
+ */
3095
+ generateSessionId() {
3096
+ return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3097
+ }
3098
+ /**
3099
+ * Destroy the SDK instance
3100
+ */
3101
+ destroy() {
3102
+ this.behavioralCollector.stop();
3103
+ this.formInteractionCollector.stop();
3104
+ this.pageInteractionCollector.stop();
3105
+ this.fingerprintManager.clearCache();
3106
+ this.isInitialized = false;
3107
+ if (this.config?.debug) {
3108
+ console.log("[Keverd SDK] Destroyed");
3109
+ }
3110
+ this.config = null;
3111
+ this.sessionId = null;
3112
+ }
3113
+ /**
3114
+ * Check if SDK is initialized and ready to use
3115
+ */
3116
+ isReady() {
3117
+ return this.isInitialized && this.config !== null;
3118
+ }
3119
+ };
3120
+ var Keverd = new KeverdSDK();
3121
+
3122
+ // src/services/keverd.service.ts
3123
+ var KeverdService = class {
3124
+ constructor(config) {
15
3125
  this.config = null;
3126
+ this.isInitialized = false;
3127
+ if (config) {
3128
+ this.init(config);
3129
+ }
16
3130
  }
3131
+ /**
3132
+ * Initialize the SDK with configuration
3133
+ * Wraps the web SDK initialization
3134
+ */
17
3135
  init(config) {
18
- this.config = config;
3136
+ if (this.isInitialized) {
3137
+ if (this.config?.debug) {
3138
+ console.warn("[Keverd SDK] Already initialized");
3139
+ }
3140
+ return;
3141
+ }
3142
+ if (!config.apiKey) {
3143
+ throw new Error("Keverd SDK: apiKey is required");
3144
+ }
3145
+ this.config = {
3146
+ endpoint: config.endpoint || this.getDefaultEndpoint(),
3147
+ debug: false,
3148
+ ...config
3149
+ };
3150
+ Keverd.init({
3151
+ apiKey: this.config.apiKey,
3152
+ endpoint: this.config.endpoint,
3153
+ userId: this.config.userId,
3154
+ debug: this.config.debug || false
3155
+ });
3156
+ this.isInitialized = true;
3157
+ if (this.config.debug) {
3158
+ console.log("[Keverd SDK] Initialized successfully", {
3159
+ endpoint: this.config.endpoint
3160
+ });
3161
+ }
19
3162
  }
3163
+ /**
3164
+ * Get visitor data (fingerprint and risk assessment)
3165
+ * Returns an RxJS Observable
3166
+ */
20
3167
  getVisitorData(options) {
21
- return from(Promise.resolve({}));
3168
+ if (!this.isInitialized || !this.config) {
3169
+ const error = {
3170
+ message: "Keverd SDK not initialized. Call init() first.",
3171
+ code: "SDK_NOT_INITIALIZED"
3172
+ };
3173
+ return throwError(() => error);
3174
+ }
3175
+ return from(this.collectAndSendFingerprint(options)).pipe(
3176
+ map((response) => this.transformResponse(response)),
3177
+ catchError((error) => {
3178
+ const keverdError = {
3179
+ message: error instanceof Error ? error.message : "Unknown error",
3180
+ code: "FETCH_ERROR"
3181
+ };
3182
+ if (this.config?.debug) {
3183
+ console.error("[Keverd SDK] Error getting visitor data:", error);
3184
+ }
3185
+ return throwError(() => keverdError);
3186
+ })
3187
+ );
3188
+ }
3189
+ /**
3190
+ * Collect data and send fingerprint request
3191
+ * Uses the web SDK which includes all enhanced signals including FingerprintJS
3192
+ */
3193
+ async collectAndSendFingerprint(_options) {
3194
+ if (!this.config) {
3195
+ throw new Error("SDK not initialized");
3196
+ }
3197
+ return await Keverd.getVisitorData();
3198
+ }
3199
+ /**
3200
+ * Transform API response to visitor data format
3201
+ */
3202
+ transformResponse(response) {
3203
+ return {
3204
+ visitorId: response.requestId,
3205
+ riskScore: response.risk_score,
3206
+ score: response.score,
3207
+ action: response.action,
3208
+ reasons: response.reason || [],
3209
+ sessionId: response.session_id,
3210
+ requestId: response.requestId,
3211
+ simSwapEngine: response.sim_swap_engine,
3212
+ confidence: response.confidence || 1 - response.score,
3213
+ // Enhanced fields
3214
+ fingerprintjsVisitorId: response.fingerprintjs_visitor_id,
3215
+ privacySignals: response.privacy_signals ? {
3216
+ isIncognito: response.privacy_signals.is_incognito,
3217
+ isVPN: response.privacy_signals.is_vpn,
3218
+ isAutomated: response.privacy_signals.is_automated,
3219
+ hasAdBlocker: response.privacy_signals.has_ad_blocker
3220
+ } : void 0,
3221
+ deviceMatch: response.device_match,
3222
+ fraudProbability: response.fraud_probability,
3223
+ recommendedAction: response.recommended_action
3224
+ };
3225
+ }
3226
+ /**
3227
+ * Get default endpoint
3228
+ */
3229
+ getDefaultEndpoint() {
3230
+ return "https://api.keverd.com";
3231
+ }
3232
+ /**
3233
+ * Generate a session ID
3234
+ */
3235
+ generateSessionId() {
3236
+ return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3237
+ }
3238
+ /**
3239
+ * Destroy the SDK instance
3240
+ */
3241
+ destroy() {
3242
+ Keverd.destroy();
3243
+ this.isInitialized = false;
3244
+ this.config = null;
3245
+ }
3246
+ /**
3247
+ * Get current configuration
3248
+ */
3249
+ getConfig() {
3250
+ return this.config;
3251
+ }
3252
+ /**
3253
+ * Check if SDK is initialized
3254
+ */
3255
+ isReady() {
3256
+ return this.isInitialized;
22
3257
  }
23
3258
  };
24
3259
  KeverdService = __decorateClass([
25
3260
  Injectable({
26
3261
  providedIn: "root"
27
- })
3262
+ }),
3263
+ __decorateParam(0, Optional()),
3264
+ __decorateParam(0, Inject("KEVERD_CONFIG"))
28
3265
  ], KeverdService);
29
3266
  var KeverdModule = class {
3267
+ /**
3268
+ * Configure the module with SDK settings
3269
+ * Call this in your app.module.ts imports array
3270
+ *
3271
+ * @example
3272
+ * ```typescript
3273
+ * @NgModule({
3274
+ * imports: [
3275
+ * KeverdModule.forRoot({
3276
+ * apiKey: 'your-api-key',
3277
+ * endpoint: 'https://api.keverd.com'
3278
+ * })
3279
+ * ]
3280
+ * })
3281
+ * export class AppModule {}
3282
+ * ```
3283
+ */
30
3284
  static forRoot(config) {
31
3285
  return {
32
3286
  ngModule: KeverdModule,
33
3287
  providers: [
34
- { provide: "KEVERD_CONFIG", useValue: config },
35
- KeverdService
3288
+ KeverdService,
3289
+ {
3290
+ provide: "KEVERD_CONFIG",
3291
+ useValue: config
3292
+ }
36
3293
  ]
37
3294
  };
38
3295
  }