@keverdjs/fraud-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +154 -0
- package/dist/collectors/behavioral.d.ts +71 -0
- package/dist/collectors/device.d.ts +76 -0
- package/dist/core/sdk.d.ts +41 -0
- package/dist/fintechrisk.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/types.d.ts +73 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Keverd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @keverdjs/fraud-sdk
|
|
2
|
+
|
|
3
|
+
Vanilla JavaScript SDK for Keverd fraud detection and device fingerprinting. Works in any JavaScript environment (browser, Node.js, etc.).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @keverdjs/fraud-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { Keverd } from '@keverdjs/fraud-sdk';
|
|
15
|
+
|
|
16
|
+
// Initialize with API key
|
|
17
|
+
Keverd.init('your-api-key');
|
|
18
|
+
|
|
19
|
+
// Get visitor data and risk assessment
|
|
20
|
+
const result = await Keverd.getVisitorData();
|
|
21
|
+
|
|
22
|
+
console.log('Risk Score:', result.risk_score);
|
|
23
|
+
console.log('Action:', result.action);
|
|
24
|
+
console.log('Session ID:', result.session_id);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### With Configuration Object
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
import { Keverd } from '@keverdjs/fraud-sdk';
|
|
31
|
+
|
|
32
|
+
// Initialize with configuration
|
|
33
|
+
Keverd.init({
|
|
34
|
+
apiKey: 'your-api-key',
|
|
35
|
+
endpoint: 'https://app.keverd.com', // Optional
|
|
36
|
+
debug: true, // Optional: enable debug logging
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Get visitor data
|
|
40
|
+
const result = await Keverd.getVisitorData();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## API Reference
|
|
44
|
+
|
|
45
|
+
### `Keverd.init(config)`
|
|
46
|
+
|
|
47
|
+
Initialize the SDK with your API key.
|
|
48
|
+
|
|
49
|
+
**Parameters:**
|
|
50
|
+
- `config` (string | SDKConfig): API key string or configuration object
|
|
51
|
+
|
|
52
|
+
**Example:**
|
|
53
|
+
```javascript
|
|
54
|
+
// Simple initialization
|
|
55
|
+
Keverd.init('your-api-key');
|
|
56
|
+
|
|
57
|
+
// With options
|
|
58
|
+
Keverd.init({
|
|
59
|
+
apiKey: 'your-api-key',
|
|
60
|
+
endpoint: 'https://app.keverd.com',
|
|
61
|
+
userId: 'optional-user-id',
|
|
62
|
+
debug: false
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `Keverd.getVisitorData()`
|
|
67
|
+
|
|
68
|
+
Get visitor data and risk assessment. This is the main method for fraud detection.
|
|
69
|
+
|
|
70
|
+
**Returns:** Promise<FingerprintResponse>
|
|
71
|
+
|
|
72
|
+
**Example:**
|
|
73
|
+
```javascript
|
|
74
|
+
const result = await Keverd.getVisitorData();
|
|
75
|
+
|
|
76
|
+
// Result structure:
|
|
77
|
+
// {
|
|
78
|
+
// risk_score: 25, // 0-100
|
|
79
|
+
// score: 0.25, // 0.0-1.0
|
|
80
|
+
// action: 'allow', // 'allow' | 'soft_challenge' | 'hard_challenge' | 'block'
|
|
81
|
+
// reason: ['new_user'], // Array of risk reasons
|
|
82
|
+
// session_id: 'uuid', // Session identifier
|
|
83
|
+
// requestId: 'uuid', // Request identifier
|
|
84
|
+
// sim_swap_engine: {...} // SIM swap detection results (null for web)
|
|
85
|
+
// }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `Keverd.createTransactionID(metadata?)`
|
|
89
|
+
|
|
90
|
+
Legacy method for backward compatibility. Returns the session ID from `getVisitorData()`.
|
|
91
|
+
|
|
92
|
+
**Parameters:**
|
|
93
|
+
- `metadata` (TransactionMetadata, optional): Transaction metadata
|
|
94
|
+
|
|
95
|
+
**Returns:** Promise<string> - Session ID
|
|
96
|
+
|
|
97
|
+
**Example:**
|
|
98
|
+
```javascript
|
|
99
|
+
const transactionId = await Keverd.createTransactionID({
|
|
100
|
+
amount: 1000,
|
|
101
|
+
currency: 'KES',
|
|
102
|
+
recipient: '254712345678'
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `Keverd.destroy()`
|
|
107
|
+
|
|
108
|
+
Destroy the SDK instance and stop all data collection.
|
|
109
|
+
|
|
110
|
+
**Example:**
|
|
111
|
+
```javascript
|
|
112
|
+
Keverd.destroy();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Response Structure
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
interface FingerprintResponse {
|
|
119
|
+
risk_score: number; // 0-100 risk score
|
|
120
|
+
score: number; // 0.0-1.0 normalized score
|
|
121
|
+
action: 'allow' | 'soft_challenge' | 'hard_challenge' | 'block';
|
|
122
|
+
reason: string[]; // Array of risk reasons
|
|
123
|
+
session_id: string; // UUID session identifier
|
|
124
|
+
requestId: string; // UUID request identifier
|
|
125
|
+
sim_swap_engine?: { // SIM swap detection (null for web SDKs)
|
|
126
|
+
userId?: string;
|
|
127
|
+
risk: number;
|
|
128
|
+
flags: {
|
|
129
|
+
sim_changed?: boolean;
|
|
130
|
+
device_changed?: boolean;
|
|
131
|
+
behavior_anomaly?: boolean;
|
|
132
|
+
time_anomaly?: boolean;
|
|
133
|
+
velocity_anomaly?: boolean;
|
|
134
|
+
};
|
|
135
|
+
updatedProfile?: Record<string, unknown>;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Browser Support
|
|
141
|
+
|
|
142
|
+
- Chrome (latest)
|
|
143
|
+
- Firefox (latest)
|
|
144
|
+
- Safari (latest)
|
|
145
|
+
- Edge (latest)
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
|
150
|
+
|
|
151
|
+
## Support
|
|
152
|
+
|
|
153
|
+
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/keverd/keverd-fraud-sdk-web).
|
|
154
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral Data Collector
|
|
3
|
+
* Collects typing patterns, mouse movements, swipe gestures, and calculates session entropy
|
|
4
|
+
* Matches React SDK implementation for consistency
|
|
5
|
+
*/
|
|
6
|
+
import { BehavioralData } from '../types';
|
|
7
|
+
export declare class BehavioralCollector {
|
|
8
|
+
private keystrokes;
|
|
9
|
+
private mouseMovements;
|
|
10
|
+
private lastKeyDownTime;
|
|
11
|
+
private lastKeyUpTime;
|
|
12
|
+
private isActive;
|
|
13
|
+
private sessionStartTime;
|
|
14
|
+
private typingDwellTimes;
|
|
15
|
+
private typingFlightTimes;
|
|
16
|
+
private swipeVelocities;
|
|
17
|
+
private sessionEvents;
|
|
18
|
+
private touchStartPositions;
|
|
19
|
+
private keyDownHandler;
|
|
20
|
+
private keyUpHandler;
|
|
21
|
+
private mouseMoveHandler;
|
|
22
|
+
private touchStartHandler;
|
|
23
|
+
private touchEndHandler;
|
|
24
|
+
/**
|
|
25
|
+
* Start collecting behavioral data
|
|
26
|
+
* Uses passive listeners for efficiency (non-blocking)
|
|
27
|
+
*/
|
|
28
|
+
start(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Stop collecting behavioral data
|
|
31
|
+
*/
|
|
32
|
+
stop(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Get collected behavioral data
|
|
35
|
+
* Returns data in format expected by backend
|
|
36
|
+
*/
|
|
37
|
+
getData(): BehavioralData;
|
|
38
|
+
/**
|
|
39
|
+
* Reset collected data
|
|
40
|
+
*/
|
|
41
|
+
reset(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Handle keydown event
|
|
44
|
+
*/
|
|
45
|
+
private handleKeyDown;
|
|
46
|
+
/**
|
|
47
|
+
* Handle keyup event
|
|
48
|
+
*/
|
|
49
|
+
private handleKeyUp;
|
|
50
|
+
/**
|
|
51
|
+
* Handle mouse move event
|
|
52
|
+
*/
|
|
53
|
+
private handleMouseMove;
|
|
54
|
+
/**
|
|
55
|
+
* Handle touch start (for swipe detection)
|
|
56
|
+
*/
|
|
57
|
+
private handleTouchStart;
|
|
58
|
+
/**
|
|
59
|
+
* Handle touch end (calculate swipe velocity)
|
|
60
|
+
*/
|
|
61
|
+
private handleTouchEnd;
|
|
62
|
+
/**
|
|
63
|
+
* Calculate session entropy based on event diversity
|
|
64
|
+
* Uses Shannon entropy formula as expected by backend
|
|
65
|
+
*/
|
|
66
|
+
private calculateSessionEntropy;
|
|
67
|
+
/**
|
|
68
|
+
* Check if key should be ignored
|
|
69
|
+
*/
|
|
70
|
+
private shouldIgnoreKey;
|
|
71
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Fingerprint Collector
|
|
3
|
+
* Collects device information and generates SHA-256 fingerprint
|
|
4
|
+
* Matches React SDK implementation for consistency
|
|
5
|
+
*/
|
|
6
|
+
import { DeviceInfo } from '../types';
|
|
7
|
+
export declare class DeviceCollector {
|
|
8
|
+
private cachedDeviceInfo;
|
|
9
|
+
/**
|
|
10
|
+
* Collect all device information and generate fingerprint
|
|
11
|
+
*/
|
|
12
|
+
collect(): DeviceInfo;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a stable device fingerprint using multiple browser characteristics
|
|
15
|
+
* Returns SHA-256 hash (64 hex characters) as required by backend
|
|
16
|
+
*/
|
|
17
|
+
private generateDeviceFingerprint;
|
|
18
|
+
/**
|
|
19
|
+
* Generate canvas fingerprint
|
|
20
|
+
*/
|
|
21
|
+
private getCanvasFingerprint;
|
|
22
|
+
/**
|
|
23
|
+
* Generate WebGL fingerprint
|
|
24
|
+
*/
|
|
25
|
+
private getWebGLFingerprint;
|
|
26
|
+
/**
|
|
27
|
+
* Generate a stable device ID from fingerprint
|
|
28
|
+
*/
|
|
29
|
+
private generateDeviceId;
|
|
30
|
+
/**
|
|
31
|
+
* Get manufacturer from user agent
|
|
32
|
+
*/
|
|
33
|
+
private getManufacturer;
|
|
34
|
+
/**
|
|
35
|
+
* Get model from user agent
|
|
36
|
+
*/
|
|
37
|
+
private getModel;
|
|
38
|
+
/**
|
|
39
|
+
* Get brand from user agent
|
|
40
|
+
*/
|
|
41
|
+
private getBrand;
|
|
42
|
+
/**
|
|
43
|
+
* Get device type
|
|
44
|
+
*/
|
|
45
|
+
private getDevice;
|
|
46
|
+
/**
|
|
47
|
+
* Get product name
|
|
48
|
+
*/
|
|
49
|
+
private getProduct;
|
|
50
|
+
/**
|
|
51
|
+
* Get hardware info
|
|
52
|
+
*/
|
|
53
|
+
private getHardware;
|
|
54
|
+
/**
|
|
55
|
+
* Get OS version
|
|
56
|
+
*/
|
|
57
|
+
private getOSVersion;
|
|
58
|
+
/**
|
|
59
|
+
* Get screen density
|
|
60
|
+
*/
|
|
61
|
+
private getScreenDensity;
|
|
62
|
+
/**
|
|
63
|
+
* Hash a string using SHA-256-like algorithm (64 hex characters)
|
|
64
|
+
* Backend expects SHA-256 format (64 hex chars)
|
|
65
|
+
*/
|
|
66
|
+
private hashString;
|
|
67
|
+
/**
|
|
68
|
+
* SHA-256-like hash function (synchronous, deterministic)
|
|
69
|
+
* Produces 64-character hex string matching SHA-256 format
|
|
70
|
+
*/
|
|
71
|
+
private sha256LikeHash;
|
|
72
|
+
/**
|
|
73
|
+
* Clear cached device info (useful for testing)
|
|
74
|
+
*/
|
|
75
|
+
clearCache(): void;
|
|
76
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keverd Fraud SDK Core (Vanilla JS)
|
|
3
|
+
* Main SDK class for fingerprinting and risk assessment
|
|
4
|
+
* Matches backend FingerprintSDKRequest format exactly
|
|
5
|
+
*/
|
|
6
|
+
import { SDKConfig, FingerprintResponse, TransactionMetadata } from '../types';
|
|
7
|
+
export declare class KeverdSDK {
|
|
8
|
+
private config;
|
|
9
|
+
private deviceCollector;
|
|
10
|
+
private behavioralCollector;
|
|
11
|
+
private isInitialized;
|
|
12
|
+
private sessionId;
|
|
13
|
+
constructor();
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the SDK with configuration
|
|
16
|
+
*/
|
|
17
|
+
init(config: SDKConfig | string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Get visitor data (fingerprint and risk assessment)
|
|
20
|
+
* This is the main method for getting risk scores
|
|
21
|
+
*/
|
|
22
|
+
getVisitorData(): Promise<FingerprintResponse>;
|
|
23
|
+
/**
|
|
24
|
+
* Create transaction ID with risk assessment (legacy method)
|
|
25
|
+
* For new implementations, use getVisitorData() instead
|
|
26
|
+
*/
|
|
27
|
+
createTransactionID(metadata?: TransactionMetadata): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Send fingerprint request to backend
|
|
30
|
+
*/
|
|
31
|
+
private sendFingerprintRequest;
|
|
32
|
+
/**
|
|
33
|
+
* Generate a unique session ID
|
|
34
|
+
*/
|
|
35
|
+
private generateSessionId;
|
|
36
|
+
/**
|
|
37
|
+
* Destroy the SDK instance
|
|
38
|
+
*/
|
|
39
|
+
destroy(): void;
|
|
40
|
+
}
|
|
41
|
+
export declare const Keverd: KeverdSDK;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.FintechRisk=e():t.FintechRisk=e()}(this,()=>(()=>{"use strict";var t={d:(e,i)=>{for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"t",{value:!0})}},e={};t.r(e),t.d(e,{BehavioralCollector:()=>s,DeviceCollector:()=>i,Keverd:()=>r,KeverdSDK:()=>n});class i{constructor(){this.cachedDeviceInfo=null}collect(){if(this.cachedDeviceInfo)return this.cachedDeviceInfo;const t=this.generateDeviceFingerprint(),e=this.generateDeviceId(t);return this.cachedDeviceInfo={deviceId:e,fingerprint:t,manufacturer:this.getManufacturer(),model:this.getModel(),brand:this.getBrand(),device:this.getDevice(),product:this.getProduct(),hardware:this.getHardware(),sdkVersion:"1.0.0",osVersion:this.getOSVersion(),screenWidth:String(screen.width),screenHeight:String(screen.height),screenDensity:this.getScreenDensity(),locale:navigator.language||navigator.languages?.[0]||"en",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone},this.cachedDeviceInfo}generateDeviceFingerprint(){const t=[],e=this.getCanvasFingerprint();e&&t.push(e);const i=this.getWebGLFingerprint();if(i&&t.push(i),t.push(navigator.userAgent),t.push(navigator.language||navigator.languages?.[0]||""),t.push(`${screen.width}x${screen.height}x${screen.colorDepth}`),t.push(String((new Date).getTimezoneOffset())),t.push(navigator.platform),t.push(String(navigator.hardwareConcurrency||0)),"deviceMemory"in navigator&&t.push(String(navigator.deviceMemory)),"maxTouchPoints"in navigator&&t.push(String(navigator.maxTouchPoints)),navigator.plugins&&navigator.plugins.length>0){const e=Array.from(navigator.plugins).slice(0,3).map(t=>t.name).join(",");t.push(e)}const s=t.join("|");return this.hashString(s)}getCanvasFingerprint(){try{const t=document.createElement("canvas"),e=t.getContext("2d");return e?(t.width=200,t.height=50,e.textBaseline="top",e.font="14px Arial",e.fillStyle="#f60",e.fillRect(125,1,62,20),e.fillStyle="#069",e.fillText("KeverdFingerprint",2,15),e.fillStyle="rgba(102, 204, 0, 0.7)",e.fillText("KeverdFingerprint",4,17),this.hashString(t.toDataURL())):""}catch(t){return""}}getWebGLFingerprint(){try{const t=document.createElement("canvas"),e=t.getContext("webgl")||t.getContext("experimental-webgl");if(!e)return"";const i=e.getExtension("WEBGL_debug_renderer_info");if(i){const t=e.getParameter(i.UNMASKED_VENDOR_WEBGL),s=e.getParameter(i.UNMASKED_RENDERER_WEBGL);return this.hashString(`${t}|${s}`)}const s=e.getParameter(e.VERSION),n=e.getParameter(e.VENDOR);return this.hashString(`${s}|${n}`)}catch(t){return""}}generateDeviceId(t){return t.substring(0,32)}getManufacturer(){const t=navigator.userAgent.toLowerCase();if(t.includes("iphone")||t.includes("ipad"))return"Apple";if(t.includes("android")){const e=t.match(/(?:^|\s)([a-z]+)(?:\s|$)/);return e?e[1].charAt(0).toUpperCase()+e[1].slice(1):void 0}}getModel(){const t=navigator.userAgent.match(/(iPhone|iPad|Android)[\s\/]+([\w]+)/i);return t?t[2]:void 0}getBrand(){return this.getManufacturer()}getDevice(){const t=navigator.userAgent.toLowerCase();return t.includes("mobile")?"mobile":t.includes("tablet")?"tablet":"desktop"}getProduct(){const t=navigator.userAgent.match(/(iPhone|iPad|Android|Windows|Mac|Linux)/i);return t?t[1]:void 0}getHardware(){const t=navigator.userAgent.match(/\(([^)]+)\)/);return t?t[1]:void 0}getOSVersion(){const t=navigator.userAgent,e=[/OS\s+([\d_]+)/i,/Android\s+([\d.]+)/i,/Windows\s+([\d.]+)/i,/Mac\s+OS\s+X\s+([\d_]+)/i,/Linux/i];for(const i of e){const e=t.match(i);if(e)return e[1]?.replace(/_/g,".")||e[0]}}getScreenDensity(){if(window.devicePixelRatio)return String(window.devicePixelRatio)}hashString(t){return this.sha256LikeHash(t)}sha256LikeHash(t){const e=[];let i=5381;for(let e=0;e<t.length;e++)i=(i<<5)+i+t.charCodeAt(e),i&=4294967295;e.push(i);let s=0;for(let e=t.length-1;e>=0;e--)s=(s<<7)-s+t.charCodeAt(e),s&=4294967295;e.push(s);let n=0;for(let e=0;e<t.length;e++)n^=t.charCodeAt(e)<<e%4*8,n&=4294967295;e.push(n);let r=0;for(let e=0;e<t.length;e++)r=31*r+t.charCodeAt(e)&4294967295;e.push(r);let o=0;for(let e=0;e<t.length;e++){o=o+(4294967295&(t.charCodeAt(e)<<e%16|t.charCodeAt(e)>>>32-e%16))&4294967295}e.push(o);let h=2166136261;for(let e=0;e<t.length;e++)h=16777619*(h^t.charCodeAt(e)),h&=4294967295;e.push(h);let a=0;for(let e=0;e<t.length;e++)a=a+t.charCodeAt(e)*(e+1)&4294967295;e.push(a);let c=0;for(let e=0;e<t.length;e+=2){c=(c<<3)-c+(t.charCodeAt(e)+256*(t.charCodeAt(e+1)||0)),c&=4294967295}e.push(c);return e.map(t=>Math.abs(t).toString(16).padStart(8,"0")).join("").substring(0,64).padEnd(64,"0")}clearCache(){this.cachedDeviceInfo=null}}class s{constructor(){this.keystrokes=[],this.mouseMovements=[],this.lastKeyDownTime=new Map,this.lastKeyUpTime=null,this.isActive=!1,this.sessionStartTime=Date.now(),this.typingDwellTimes=[],this.typingFlightTimes=[],this.swipeVelocities=[],this.sessionEvents=[],this.touchStartPositions=new Map,this.keyDownHandler=t=>this.handleKeyDown(t),this.keyUpHandler=t=>this.handleKeyUp(t),this.mouseMoveHandler=t=>this.handleMouseMove(t),this.touchStartHandler=t=>this.handleTouchStart(t),this.touchEndHandler=t=>this.handleTouchEnd(t)}start(){this.isActive||("undefined"!=typeof document&&(document.addEventListener("keydown",this.keyDownHandler,{passive:!0}),document.addEventListener("keyup",this.keyUpHandler,{passive:!0}),document.addEventListener("mousemove",this.mouseMoveHandler,{passive:!0}),document.addEventListener("touchstart",this.touchStartHandler,{passive:!0}),document.addEventListener("touchend",this.touchEndHandler,{passive:!0})),this.isActive=!0,this.sessionStartTime=Date.now())}stop(){this.isActive&&("undefined"!=typeof document&&(document.removeEventListener("keydown",this.keyDownHandler),document.removeEventListener("keyup",this.keyUpHandler),document.removeEventListener("mousemove",this.mouseMoveHandler),document.removeEventListener("touchstart",this.touchStartHandler),document.removeEventListener("touchend",this.touchEndHandler)),this.isActive=!1)}getData(){const t=this.typingDwellTimes.slice(-20),e=this.typingFlightTimes.slice(-20),i=this.swipeVelocities.length>0?this.swipeVelocities.reduce((t,e)=>t+e,0)/this.swipeVelocities.length:0,s=this.calculateSessionEntropy();return{typing_dwell_ms:t.length>0?t:[],typing_flight_ms:e.length>0?e:[],swipe_velocity:i>0?i:0,session_entropy:s>=0?s:0}}reset(){this.keystrokes=[],this.mouseMovements=[],this.lastKeyDownTime.clear(),this.lastKeyUpTime=null,this.typingDwellTimes=[],this.typingFlightTimes=[],this.swipeVelocities=[],this.sessionEvents=[],this.touchStartPositions.clear(),this.sessionStartTime=Date.now()}handleKeyDown(t){if(this.shouldIgnoreKey(t))return;const e=performance.now();this.lastKeyDownTime.set(t.key,e);const i={key:t.key,timestamp:e,type:"keydown"};this.keystrokes.push(i),this.sessionEvents.push({type:"keydown",timestamp:e})}handleKeyUp(t){if(this.shouldIgnoreKey(t))return;const e=performance.now(),i=this.lastKeyDownTime.get(t.key);if(void 0!==i){const s=e-i;this.typingDwellTimes.push(s),this.lastKeyDownTime.delete(t.key)}if(null!==this.lastKeyUpTime){const t=e-this.lastKeyUpTime;this.typingFlightTimes.push(t)}this.lastKeyUpTime=e;const s={key:t.key,timestamp:e,type:"keyup"};this.keystrokes.push(s),this.sessionEvents.push({type:"keyup",timestamp:e})}handleMouseMove(t){const e={x:t.clientX,y:t.clientY,timestamp:performance.now(),type:"move"};if(this.mouseMovements.length>0){const t=this.mouseMovements[this.mouseMovements.length-1],i=e.timestamp-t.timestamp;if(i>0){const s=Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2));e.velocity=s/i}}this.mouseMovements.push(e),this.sessionEvents.push({type:"mousemove",timestamp:e.timestamp})}handleTouchStart(t){const e=performance.now();for(let i=0;i<t.touches.length;i++){const s=t.touches[i];this.touchStartPositions.set(s.identifier,{x:s.clientX,y:s.clientY,timestamp:e})}this.sessionEvents.push({type:"touchstart",timestamp:e})}handleTouchEnd(t){const e=performance.now();for(let i=0;i<t.changedTouches.length;i++){const s=t.changedTouches[i],n=this.touchStartPositions.get(s.identifier);if(n){const t=e-n.timestamp;if(t>0&&t<1e3){const e=Math.sqrt(Math.pow(s.clientX-n.x,2)+Math.pow(s.clientY-n.y,2));if(e>10){const i=e/t;this.swipeVelocities.push(i)}}this.touchStartPositions.delete(s.identifier)}}this.sessionEvents.push({type:"touchend",timestamp:e})}calculateSessionEntropy(){if(0===this.sessionEvents.length)return 0;const t={};for(const e of this.sessionEvents)t[e.type]=(t[e.type]||0)+1;const e=this.sessionEvents.length;let i=0;for(const s of Object.values(t)){const t=s/e;t>0&&(i-=t*Math.log2(t))}return i}shouldIgnoreKey(t){return["Shift","Control","Alt","Meta","CapsLock","Tab","Escape","Enter","ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Home","End","PageUp","PageDown","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12"].includes(t.key)}}class n{constructor(){this.config=null,this.isInitialized=!1,this.sessionId=null,this.deviceCollector=new i,this.behavioralCollector=new s}init(t){this.isInitialized?this.config:(this.config="string"==typeof t?{apiKey:t,endpoint:"https://app.keverd.com",debug:!1}:{endpoint:"https://app.keverd.com",debug:!1,...t},this.behavioralCollector.start(),this.sessionId=this.generateSessionId(),this.isInitialized=!0,this.config.debug)}async getVisitorData(){if(!this.isInitialized||!this.config)throw new Error("Keverd SDK not initialized. Call init() first.");try{const t=this.deviceCollector.collect(),e=this.behavioralCollector.getData(),i={sessionId:this.sessionId||void 0,timestamp:(new Date).toISOString()},s={userId:this.config.userId,device:t,session:i,behavioral:e};return await this.sendFingerprintRequest(s)}catch(t){const e=t instanceof Error?t.message:"Unknown error";throw this.config.debug,new Error(`Failed to get visitor data: ${e}`)}}async createTransactionID(t){return(await this.getVisitorData()).session_id}async sendFingerprintRequest(t){if(!this.config)throw new Error("SDK not initialized");const e=`${this.config.endpoint||"https://app.keverd.com"}/fingerprint/score`,i={"Content-Type":"application/json","X-SDK-Source":"javascript"},s=this.config.apiKey;s&&(i["x-keverd-key"]=s,i["X-API-KEY"]=s,i.Authorization=`Bearer ${s}`);const n=await fetch(e,{method:"POST",headers:i,body:JSON.stringify(t)});if(!n.ok){const t=await n.text().catch(()=>"Unknown error");throw new Error(`HTTP ${n.status}: ${t}`)}return await n.json()}generateSessionId(){return`${Date.now()}_${Math.random().toString(36).substr(2,9)}`}destroy(){this.behavioralCollector.stop(),this.isInitialized=!1,this.config,this.config=null,this.sessionId=null}}const r=new n;return e})());
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keverd Fraud SDK (Vanilla JS)
|
|
3
|
+
* Main entry point
|
|
4
|
+
*/
|
|
5
|
+
export { KeverdSDK, Keverd } from './core/sdk';
|
|
6
|
+
export { DeviceCollector } from './collectors/device';
|
|
7
|
+
export { BehavioralCollector } from './collectors/behavioral';
|
|
8
|
+
export type { SDKConfig, DeviceInfo, SessionInfo, BehavioralData, FingerprintRequest, FingerprintResponse, TransactionMetadata, } from './types';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Keverd Fraud SDK (Vanilla JS)
|
|
3
|
+
* Matches backend FingerprintSDKRequest schema exactly
|
|
4
|
+
*/
|
|
5
|
+
export interface DeviceInfo {
|
|
6
|
+
deviceId: string;
|
|
7
|
+
fingerprint: string;
|
|
8
|
+
manufacturer?: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
brand?: string;
|
|
11
|
+
device?: string;
|
|
12
|
+
product?: string;
|
|
13
|
+
hardware?: string;
|
|
14
|
+
sdkVersion?: string;
|
|
15
|
+
osVersion?: string;
|
|
16
|
+
screenWidth?: string;
|
|
17
|
+
screenHeight?: string;
|
|
18
|
+
screenDensity?: string;
|
|
19
|
+
locale?: string;
|
|
20
|
+
timezone: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SessionInfo {
|
|
23
|
+
sessionId?: string;
|
|
24
|
+
installId?: string;
|
|
25
|
+
sessionCount?: string;
|
|
26
|
+
firstSession?: string;
|
|
27
|
+
timestamp?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface BehavioralData {
|
|
30
|
+
typing_dwell_ms?: number[];
|
|
31
|
+
typing_flight_ms?: number[];
|
|
32
|
+
swipe_velocity?: number;
|
|
33
|
+
session_entropy?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface FingerprintRequest {
|
|
36
|
+
userId?: string;
|
|
37
|
+
device: DeviceInfo;
|
|
38
|
+
session?: SessionInfo;
|
|
39
|
+
behavioral?: BehavioralData;
|
|
40
|
+
}
|
|
41
|
+
export interface FingerprintResponse {
|
|
42
|
+
risk_score: number;
|
|
43
|
+
score: number;
|
|
44
|
+
action: 'allow' | 'soft_challenge' | 'hard_challenge' | 'block';
|
|
45
|
+
reason: string[];
|
|
46
|
+
session_id: string;
|
|
47
|
+
requestId: string;
|
|
48
|
+
sim_swap_engine?: {
|
|
49
|
+
userId?: string;
|
|
50
|
+
risk: number;
|
|
51
|
+
flags: {
|
|
52
|
+
sim_changed?: boolean;
|
|
53
|
+
device_changed?: boolean;
|
|
54
|
+
behavior_anomaly?: boolean;
|
|
55
|
+
time_anomaly?: boolean;
|
|
56
|
+
velocity_anomaly?: boolean;
|
|
57
|
+
};
|
|
58
|
+
updatedProfile?: Record<string, unknown>;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface SDKConfig {
|
|
62
|
+
apiKey: string;
|
|
63
|
+
endpoint?: string;
|
|
64
|
+
userId?: string;
|
|
65
|
+
debug?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface TransactionMetadata {
|
|
68
|
+
amount?: number;
|
|
69
|
+
currency?: string;
|
|
70
|
+
recipient?: string;
|
|
71
|
+
transactionType?: string;
|
|
72
|
+
customData?: Record<string, unknown>;
|
|
73
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keverdjs/fraud-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Vanilla JavaScript SDK for Keverd fraud detection and device fingerprinting",
|
|
5
|
+
"main": "dist/fintechrisk.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"module": "dist/fintechrisk.esm.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "webpack --mode=production",
|
|
15
|
+
"dev": "webpack --mode=development --watch",
|
|
16
|
+
"lint": "eslint src/**/*.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"keverd",
|
|
24
|
+
"fraud-detection",
|
|
25
|
+
"device-fingerprinting",
|
|
26
|
+
"behavioral-biometrics",
|
|
27
|
+
"vanilla-js",
|
|
28
|
+
"javascript",
|
|
29
|
+
"risk-assessment"
|
|
30
|
+
],
|
|
31
|
+
"author": "Keverd",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/keverd/keverd-fraud-sdk-web.git",
|
|
36
|
+
"directory": "packages/@keverd/fraud-sdk"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
43
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
44
|
+
"eslint": "^8.45.0",
|
|
45
|
+
"jest": "^29.6.0",
|
|
46
|
+
"ts-jest": "^29.1.0",
|
|
47
|
+
"typescript": "^5.1.6",
|
|
48
|
+
"webpack": "^5.88.0",
|
|
49
|
+
"webpack-cli": "^5.1.0",
|
|
50
|
+
"terser-webpack-plugin": "^5.3.9",
|
|
51
|
+
"ts-loader": "^9.4.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|