@keverdjs/fraud-sdk-angular 1.0.0 → 2.0.0

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