@keverdjs/fraud-sdk-angular 1.0.0 → 1.1.2

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