@hexar/biometric-identity-sdk-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BiometricIdentitySDK.d.ts +111 -0
- package/dist/BiometricIdentitySDK.js +395 -0
- package/dist/ai-models/FaceDetector.d.ts +59 -0
- package/dist/ai-models/FaceDetector.js +167 -0
- package/dist/ai-models/LivenessDetector.d.ts +61 -0
- package/dist/ai-models/LivenessDetector.js +218 -0
- package/dist/api/BackendClient.d.ts +178 -0
- package/dist/api/BackendClient.js +199 -0
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.js +8 -0
- package/dist/encryption/index.d.ts +38 -0
- package/dist/encryption/index.js +99 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.js +47 -0
- package/dist/i18n/languages/en.d.ts +2 -0
- package/dist/i18n/languages/en.js +112 -0
- package/dist/i18n/languages/es-AR.d.ts +2 -0
- package/dist/i18n/languages/es-AR.js +112 -0
- package/dist/i18n/languages/es.d.ts +2 -0
- package/dist/i18n/languages/es.js +112 -0
- package/dist/i18n/languages/pt-BR.d.ts +2 -0
- package/dist/i18n/languages/pt-BR.js +112 -0
- package/dist/i18n/types.d.ts +110 -0
- package/dist/i18n/types.js +2 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +64 -0
- package/dist/services/BackendValidationService.d.ts +84 -0
- package/dist/services/BackendValidationService.js +174 -0
- package/dist/services/IValidationService.d.ts +132 -0
- package/dist/services/IValidationService.js +8 -0
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.js +10 -0
- package/dist/types/index.d.ts +288 -0
- package/dist/types/index.js +34 -0
- package/dist/validation/DocumentValidator.d.ts +84 -0
- package/dist/validation/DocumentValidator.js +295 -0
- package/dist/validation/OCREngine.d.ts +75 -0
- package/dist/validation/OCREngine.js +225 -0
- package/package.json +24 -0
- package/src/BiometricIdentitySDK.ts +493 -0
- package/src/ai-models/FaceDetector.ts +200 -0
- package/src/ai-models/LivenessDetector.ts +274 -0
- package/src/api/BackendClient.ts +395 -0
- package/src/api/index.ts +15 -0
- package/src/encryption/index.ts +108 -0
- package/src/i18n/index.ts +35 -0
- package/src/i18n/languages/en.ts +121 -0
- package/src/i18n/languages/es-AR.ts +121 -0
- package/src/i18n/languages/es.ts +121 -0
- package/src/i18n/languages/pt-BR.ts +121 -0
- package/src/i18n/types.ts +121 -0
- package/src/index.ts +54 -0
- package/src/services/BackendValidationService.ts +228 -0
- package/src/services/IValidationService.ts +158 -0
- package/src/services/index.ts +17 -0
- package/src/types/index.ts +380 -0
- package/src/validation/DocumentValidator.ts +353 -0
- package/src/validation/OCREngine.ts +265 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for Biometric Identity SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ValidationResult {
|
|
6
|
+
/** Face match confidence score (0-100) */
|
|
7
|
+
matchScore: number;
|
|
8
|
+
|
|
9
|
+
/** Whether face from video matches face in document */
|
|
10
|
+
isValidFaceMatch: boolean;
|
|
11
|
+
|
|
12
|
+
/** Whether document passes authenticity checks */
|
|
13
|
+
isDocumentAuthentic: boolean;
|
|
14
|
+
|
|
15
|
+
/** Liveness detection confidence score (0-100) */
|
|
16
|
+
livenessScore: number;
|
|
17
|
+
|
|
18
|
+
/** Extracted data from document OCR */
|
|
19
|
+
extractedDocumentData: DocumentData;
|
|
20
|
+
|
|
21
|
+
/** List of warnings detected during validation */
|
|
22
|
+
warnings: string[];
|
|
23
|
+
|
|
24
|
+
/** Timestamp of validation */
|
|
25
|
+
timestamp: number;
|
|
26
|
+
|
|
27
|
+
/** Detailed breakdown of validation steps */
|
|
28
|
+
details?: ValidationDetails;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DocumentData {
|
|
32
|
+
firstName: string;
|
|
33
|
+
lastName: string;
|
|
34
|
+
documentNumber: string;
|
|
35
|
+
dateOfBirth: string;
|
|
36
|
+
expirationDate: string;
|
|
37
|
+
nationality: string;
|
|
38
|
+
documentType?: string;
|
|
39
|
+
gender?: string;
|
|
40
|
+
address?: string;
|
|
41
|
+
issuingCountry?: string;
|
|
42
|
+
rawText?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ValidationDetails {
|
|
46
|
+
faceDetection: {
|
|
47
|
+
detected: boolean;
|
|
48
|
+
confidence: number;
|
|
49
|
+
boundingBox?: BoundingBox;
|
|
50
|
+
};
|
|
51
|
+
documentQuality: {
|
|
52
|
+
hasGlare: boolean;
|
|
53
|
+
hasBlur: boolean;
|
|
54
|
+
hasCropping: boolean;
|
|
55
|
+
hasReflections: boolean;
|
|
56
|
+
edgeDetectionScore: number;
|
|
57
|
+
};
|
|
58
|
+
tamperDetection: {
|
|
59
|
+
isTampered: boolean;
|
|
60
|
+
tamperScore: number;
|
|
61
|
+
suspiciousRegions: BoundingBox[];
|
|
62
|
+
};
|
|
63
|
+
hologramDetection?: {
|
|
64
|
+
detected: boolean;
|
|
65
|
+
confidence: number;
|
|
66
|
+
};
|
|
67
|
+
livenessChecks: {
|
|
68
|
+
passedMotionCheck: boolean;
|
|
69
|
+
passedTextureCheck: boolean;
|
|
70
|
+
passedDepthCheck: boolean;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface BoundingBox {
|
|
75
|
+
x: number;
|
|
76
|
+
y: number;
|
|
77
|
+
width: number;
|
|
78
|
+
height: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
import { SupportedLanguage } from '../i18n';
|
|
82
|
+
|
|
83
|
+
export interface BiometricConfig {
|
|
84
|
+
apiEndpoint?: string;
|
|
85
|
+
apiKey?: string;
|
|
86
|
+
language?: SupportedLanguage;
|
|
87
|
+
enableBackendValidation?: boolean;
|
|
88
|
+
enableLocalStorage?: boolean;
|
|
89
|
+
encryptionKey?: string;
|
|
90
|
+
minMatchScore?: number;
|
|
91
|
+
minLivenessScore?: number;
|
|
92
|
+
validationTimeout?: number;
|
|
93
|
+
modelPaths?: {
|
|
94
|
+
faceDetection?: string;
|
|
95
|
+
faceRecognition?: string;
|
|
96
|
+
livenessDetection?: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ThemeConfig {
|
|
101
|
+
/** Primary brand color */
|
|
102
|
+
primaryColor?: string;
|
|
103
|
+
|
|
104
|
+
/** Background color */
|
|
105
|
+
backgroundColor?: string;
|
|
106
|
+
|
|
107
|
+
/** Text color */
|
|
108
|
+
textColor?: string;
|
|
109
|
+
|
|
110
|
+
/** Secondary text color */
|
|
111
|
+
secondaryTextColor?: string;
|
|
112
|
+
|
|
113
|
+
/** Button border radius */
|
|
114
|
+
buttonRadius?: number;
|
|
115
|
+
|
|
116
|
+
/** Font family */
|
|
117
|
+
fontFamily?: string;
|
|
118
|
+
|
|
119
|
+
/** Custom logo */
|
|
120
|
+
logo?: string | any;
|
|
121
|
+
|
|
122
|
+
/** Success color */
|
|
123
|
+
successColor?: string;
|
|
124
|
+
|
|
125
|
+
/** Error color */
|
|
126
|
+
errorColor?: string;
|
|
127
|
+
|
|
128
|
+
/** Warning color */
|
|
129
|
+
warningColor?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface VideoLivenessOptions {
|
|
133
|
+
/** Duration of video recording in milliseconds */
|
|
134
|
+
duration?: number;
|
|
135
|
+
|
|
136
|
+
/** Instructions for user to follow */
|
|
137
|
+
instructions?: LivenessInstruction[];
|
|
138
|
+
|
|
139
|
+
/** Frame rate for video capture */
|
|
140
|
+
frameRate?: number;
|
|
141
|
+
|
|
142
|
+
/** Quality of video (0-1) */
|
|
143
|
+
quality?: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type LivenessInstruction =
|
|
147
|
+
| 'look_left'
|
|
148
|
+
| 'look_right'
|
|
149
|
+
| 'look_up'
|
|
150
|
+
| 'look_down'
|
|
151
|
+
| 'smile'
|
|
152
|
+
| 'blink'
|
|
153
|
+
| 'turn_head_left'
|
|
154
|
+
| 'turn_head_right';
|
|
155
|
+
|
|
156
|
+
export interface VideoResult {
|
|
157
|
+
/** Video frames as base64 encoded images */
|
|
158
|
+
frames: string[];
|
|
159
|
+
|
|
160
|
+
/** Duration of captured video */
|
|
161
|
+
duration: number;
|
|
162
|
+
|
|
163
|
+
/** Whether all instructions were followed */
|
|
164
|
+
instructionsFollowed: boolean;
|
|
165
|
+
|
|
166
|
+
/** Quality score of the video */
|
|
167
|
+
qualityScore: number;
|
|
168
|
+
|
|
169
|
+
/** List of completed challenge actions (for active liveness) */
|
|
170
|
+
challengesCompleted?: string[];
|
|
171
|
+
|
|
172
|
+
/** Session ID from backend challenge (if using backend) */
|
|
173
|
+
sessionId?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ImageData {
|
|
177
|
+
/** Base64 encoded image data */
|
|
178
|
+
data: string;
|
|
179
|
+
|
|
180
|
+
/** Image width */
|
|
181
|
+
width: number;
|
|
182
|
+
|
|
183
|
+
/** Image height */
|
|
184
|
+
height: number;
|
|
185
|
+
|
|
186
|
+
/** MIME type */
|
|
187
|
+
mimeType: string;
|
|
188
|
+
|
|
189
|
+
/** File size in bytes */
|
|
190
|
+
size: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface EncryptedData {
|
|
194
|
+
/** Encrypted data payload */
|
|
195
|
+
data: string;
|
|
196
|
+
|
|
197
|
+
/** Initialization vector */
|
|
198
|
+
iv: string;
|
|
199
|
+
|
|
200
|
+
/** Authentication tag */
|
|
201
|
+
tag?: string;
|
|
202
|
+
|
|
203
|
+
/** Encryption algorithm used */
|
|
204
|
+
algorithm: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface BiometricError extends Error {
|
|
208
|
+
code: BiometricErrorCode;
|
|
209
|
+
details?: any;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export enum BiometricErrorCode {
|
|
213
|
+
CAMERA_PERMISSION_DENIED = 'CAMERA_PERMISSION_DENIED',
|
|
214
|
+
CAMERA_NOT_AVAILABLE = 'CAMERA_NOT_AVAILABLE',
|
|
215
|
+
FACE_NOT_DETECTED = 'FACE_NOT_DETECTED',
|
|
216
|
+
MULTIPLE_FACES_DETECTED = 'MULTIPLE_FACES_DETECTED',
|
|
217
|
+
DOCUMENT_NOT_DETECTED = 'DOCUMENT_NOT_DETECTED',
|
|
218
|
+
POOR_IMAGE_QUALITY = 'POOR_IMAGE_QUALITY',
|
|
219
|
+
LIVENESS_CHECK_FAILED = 'LIVENESS_CHECK_FAILED',
|
|
220
|
+
FACE_MATCH_FAILED = 'FACE_MATCH_FAILED',
|
|
221
|
+
DOCUMENT_TAMPERED = 'DOCUMENT_TAMPERED',
|
|
222
|
+
OCR_FAILED = 'OCR_FAILED',
|
|
223
|
+
VALIDATION_TIMEOUT = 'VALIDATION_TIMEOUT',
|
|
224
|
+
NETWORK_ERROR = 'NETWORK_ERROR',
|
|
225
|
+
MODEL_LOAD_FAILED = 'MODEL_LOAD_FAILED',
|
|
226
|
+
ENCRYPTION_FAILED = 'ENCRYPTION_FAILED',
|
|
227
|
+
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface FaceEmbedding {
|
|
231
|
+
/** 512-dimensional face embedding vector */
|
|
232
|
+
vector: number[];
|
|
233
|
+
|
|
234
|
+
/** Confidence score of face detection */
|
|
235
|
+
confidence: number;
|
|
236
|
+
|
|
237
|
+
/** Bounding box of detected face */
|
|
238
|
+
boundingBox: BoundingBox;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface DocumentValidationResult {
|
|
242
|
+
/** Whether document is authentic */
|
|
243
|
+
isAuthentic: boolean;
|
|
244
|
+
|
|
245
|
+
/** Overall authenticity score (0-100) */
|
|
246
|
+
authenticityScore: number;
|
|
247
|
+
|
|
248
|
+
/** Quality assessment */
|
|
249
|
+
quality: {
|
|
250
|
+
hasGlare: boolean;
|
|
251
|
+
hasBlur: boolean;
|
|
252
|
+
hasCropping: boolean;
|
|
253
|
+
hasReflections: boolean;
|
|
254
|
+
edgeScore: number;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/** Tamper detection results */
|
|
258
|
+
tamper: {
|
|
259
|
+
detected: boolean;
|
|
260
|
+
score: number;
|
|
261
|
+
regions: BoundingBox[];
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** Hologram detection (if applicable) */
|
|
265
|
+
hologram?: {
|
|
266
|
+
detected: boolean;
|
|
267
|
+
confidence: number;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface LivenessValidationResult {
|
|
272
|
+
/** Whether liveness check passed */
|
|
273
|
+
isLive: boolean;
|
|
274
|
+
|
|
275
|
+
/** Overall liveness score (0-100) */
|
|
276
|
+
livenessScore: number;
|
|
277
|
+
|
|
278
|
+
/** Individual check results */
|
|
279
|
+
checks: {
|
|
280
|
+
motionCheck: boolean;
|
|
281
|
+
textureCheck: boolean;
|
|
282
|
+
depthCheck: boolean;
|
|
283
|
+
blinkCheck?: boolean;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/** Detected face in video */
|
|
287
|
+
faceEmbedding: FaceEmbedding;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface SDKState {
|
|
291
|
+
/** Current step in the flow */
|
|
292
|
+
currentStep: SDKStep;
|
|
293
|
+
|
|
294
|
+
/** Front ID image */
|
|
295
|
+
frontID?: ImageData;
|
|
296
|
+
|
|
297
|
+
/** Back ID image */
|
|
298
|
+
backID?: ImageData;
|
|
299
|
+
|
|
300
|
+
/** Video liveness data */
|
|
301
|
+
videoData?: VideoResult;
|
|
302
|
+
|
|
303
|
+
/** Validation result */
|
|
304
|
+
validationResult?: ValidationResult;
|
|
305
|
+
|
|
306
|
+
/** Error state */
|
|
307
|
+
error?: BiometricError;
|
|
308
|
+
|
|
309
|
+
/** Loading state */
|
|
310
|
+
isLoading: boolean;
|
|
311
|
+
|
|
312
|
+
/** Progress percentage (0-100) */
|
|
313
|
+
progress: number;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export enum SDKStep {
|
|
317
|
+
INIT = 'INIT',
|
|
318
|
+
CAPTURE_FRONT_ID = 'CAPTURE_FRONT_ID',
|
|
319
|
+
CAPTURE_BACK_ID = 'CAPTURE_BACK_ID',
|
|
320
|
+
RECORD_LIVENESS = 'RECORD_LIVENESS',
|
|
321
|
+
VALIDATING = 'VALIDATING',
|
|
322
|
+
RESULT = 'RESULT',
|
|
323
|
+
ERROR = 'ERROR',
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface ModelConfig {
|
|
327
|
+
/** Model name */
|
|
328
|
+
name: string;
|
|
329
|
+
|
|
330
|
+
/** Model path or URL */
|
|
331
|
+
path: string;
|
|
332
|
+
|
|
333
|
+
/** Model version */
|
|
334
|
+
version: string;
|
|
335
|
+
|
|
336
|
+
/** Input shape */
|
|
337
|
+
inputShape: number[];
|
|
338
|
+
|
|
339
|
+
/** Output shape */
|
|
340
|
+
outputShape: number[];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface BackendValidationRequest {
|
|
344
|
+
/** Encrypted front ID image */
|
|
345
|
+
frontID: EncryptedData;
|
|
346
|
+
|
|
347
|
+
/** Encrypted back ID image */
|
|
348
|
+
backID: EncryptedData;
|
|
349
|
+
|
|
350
|
+
/** Encrypted video frames */
|
|
351
|
+
videoFrames: EncryptedData;
|
|
352
|
+
|
|
353
|
+
/** Local validation result */
|
|
354
|
+
localValidationResult: ValidationResult;
|
|
355
|
+
|
|
356
|
+
/** SDK version */
|
|
357
|
+
sdkVersion: string;
|
|
358
|
+
|
|
359
|
+
/** Timestamp */
|
|
360
|
+
timestamp: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export interface BackendValidationResponse {
|
|
364
|
+
/** Whether validation was approved */
|
|
365
|
+
approved: boolean;
|
|
366
|
+
|
|
367
|
+
/** Match score from backend */
|
|
368
|
+
matchScore: number;
|
|
369
|
+
|
|
370
|
+
/** Additional checks performed by backend */
|
|
371
|
+
additionalChecks?: Record<string, any>;
|
|
372
|
+
|
|
373
|
+
/** Backend validation timestamp */
|
|
374
|
+
timestamp: number;
|
|
375
|
+
|
|
376
|
+
/** Backend session ID */
|
|
377
|
+
sessionId: string;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Validation - Detect tampering, quality issues, and authenticity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { DocumentValidationResult, BoundingBox } from '../types';
|
|
6
|
+
|
|
7
|
+
export class DocumentValidator {
|
|
8
|
+
private model: any;
|
|
9
|
+
private isModelLoaded: boolean = false;
|
|
10
|
+
|
|
11
|
+
constructor(private modelPath?: string) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load document validation models
|
|
15
|
+
*/
|
|
16
|
+
async loadModel(): Promise<void> {
|
|
17
|
+
if (this.isModelLoaded) return;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
console.log('Loading document validation model...');
|
|
21
|
+
|
|
22
|
+
// Simulate model loading
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
24
|
+
|
|
25
|
+
this.isModelLoaded = true;
|
|
26
|
+
console.log('Document validation model loaded successfully');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new Error(`Failed to load document validation model: ${error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate document authenticity and quality
|
|
34
|
+
*/
|
|
35
|
+
async validateDocument(imageData: string | Buffer): Promise<DocumentValidationResult> {
|
|
36
|
+
if (!this.isModelLoaded) {
|
|
37
|
+
await this.loadModel();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Perform parallel quality checks
|
|
42
|
+
const [qualityResult, tamperResult, hologramResult] = await Promise.all([
|
|
43
|
+
this.checkQuality(imageData),
|
|
44
|
+
this.detectTampering(imageData),
|
|
45
|
+
this.detectHologram(imageData),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// Calculate overall authenticity score
|
|
49
|
+
const authenticityScore = this.calculateAuthenticityScore(
|
|
50
|
+
qualityResult,
|
|
51
|
+
tamperResult,
|
|
52
|
+
hologramResult
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const isAuthentic = authenticityScore >= 70 && !tamperResult.detected;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
isAuthentic,
|
|
59
|
+
authenticityScore,
|
|
60
|
+
quality: qualityResult,
|
|
61
|
+
tamper: tamperResult,
|
|
62
|
+
hologram: hologramResult,
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`Document validation failed: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check document image quality
|
|
71
|
+
*/
|
|
72
|
+
private async checkQuality(imageData: string | Buffer): Promise<{
|
|
73
|
+
hasGlare: boolean;
|
|
74
|
+
hasBlur: boolean;
|
|
75
|
+
hasCropping: boolean;
|
|
76
|
+
hasReflections: boolean;
|
|
77
|
+
edgeScore: number;
|
|
78
|
+
}> {
|
|
79
|
+
// In real implementation:
|
|
80
|
+
// 1. Detect bright spots (glare)
|
|
81
|
+
// 2. Calculate blur metric (Laplacian variance)
|
|
82
|
+
// 3. Detect document edges and check if complete
|
|
83
|
+
// 4. Detect reflective areas
|
|
84
|
+
|
|
85
|
+
const hasGlare = await this.detectGlare(imageData);
|
|
86
|
+
const hasBlur = await this.detectBlur(imageData);
|
|
87
|
+
const hasCropping = await this.detectCropping(imageData);
|
|
88
|
+
const hasReflections = await this.detectReflections(imageData);
|
|
89
|
+
const edgeScore = await this.calculateEdgeScore(imageData);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
hasGlare,
|
|
93
|
+
hasBlur,
|
|
94
|
+
hasCropping,
|
|
95
|
+
hasReflections,
|
|
96
|
+
edgeScore,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detect glare in document image
|
|
102
|
+
*/
|
|
103
|
+
private async detectGlare(imageData: string | Buffer): Promise<boolean> {
|
|
104
|
+
// In real implementation:
|
|
105
|
+
// 1. Convert to grayscale
|
|
106
|
+
// 2. Find pixels above high threshold (> 240)
|
|
107
|
+
// 3. Calculate percentage of bright pixels
|
|
108
|
+
// 4. Check if bright regions form patches (glare patterns)
|
|
109
|
+
|
|
110
|
+
// Mock implementation
|
|
111
|
+
const randomScore = Math.random();
|
|
112
|
+
return randomScore < 0.15; // 15% chance of glare
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Detect blur in document image
|
|
117
|
+
*/
|
|
118
|
+
private async detectBlur(imageData: string | Buffer): Promise<boolean> {
|
|
119
|
+
// In real implementation:
|
|
120
|
+
// 1. Apply Laplacian operator
|
|
121
|
+
// 2. Calculate variance of Laplacian
|
|
122
|
+
// 3. Low variance indicates blur
|
|
123
|
+
|
|
124
|
+
// Mock implementation
|
|
125
|
+
const randomScore = Math.random();
|
|
126
|
+
return randomScore < 0.10; // 10% chance of blur
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Detect if document is cropped
|
|
131
|
+
*/
|
|
132
|
+
private async detectCropping(imageData: string | Buffer): Promise<boolean> {
|
|
133
|
+
// In real implementation:
|
|
134
|
+
// 1. Detect document edges using Canny edge detection
|
|
135
|
+
// 2. Find document contours
|
|
136
|
+
// 3. Check if all four corners are visible
|
|
137
|
+
|
|
138
|
+
// Mock implementation
|
|
139
|
+
const randomScore = Math.random();
|
|
140
|
+
return randomScore < 0.08; // 8% chance of cropping
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Detect reflections on document
|
|
145
|
+
*/
|
|
146
|
+
private async detectReflections(imageData: string | Buffer): Promise<boolean> {
|
|
147
|
+
// In real implementation:
|
|
148
|
+
// 1. Analyze specular highlights
|
|
149
|
+
// 2. Detect glossy surface patterns
|
|
150
|
+
// 3. Check for uneven lighting
|
|
151
|
+
|
|
152
|
+
// Mock implementation
|
|
153
|
+
const randomScore = Math.random();
|
|
154
|
+
return randomScore < 0.12; // 12% chance of reflections
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Calculate edge detection score
|
|
159
|
+
*/
|
|
160
|
+
private async calculateEdgeScore(imageData: string | Buffer): Promise<number> {
|
|
161
|
+
// In real implementation:
|
|
162
|
+
// 1. Apply Canny edge detection
|
|
163
|
+
// 2. Calculate edge pixel density
|
|
164
|
+
// 3. Normalize to 0-100 scale
|
|
165
|
+
|
|
166
|
+
// Mock implementation - random score between 70-98
|
|
167
|
+
return Math.round(70 + Math.random() * 28);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Detect document tampering
|
|
172
|
+
*/
|
|
173
|
+
private async detectTampering(imageData: string | Buffer): Promise<{
|
|
174
|
+
detected: boolean;
|
|
175
|
+
score: number;
|
|
176
|
+
regions: BoundingBox[];
|
|
177
|
+
}> {
|
|
178
|
+
// In real implementation:
|
|
179
|
+
// 1. Error Level Analysis (ELA)
|
|
180
|
+
// 2. Clone detection
|
|
181
|
+
// 3. JPEG artifact analysis
|
|
182
|
+
// 4. Metadata analysis
|
|
183
|
+
// 5. Font consistency check
|
|
184
|
+
// 6. Color histogram analysis
|
|
185
|
+
|
|
186
|
+
const tamperScore = await this.calculateTamperScore(imageData);
|
|
187
|
+
const suspiciousRegions = await this.findSuspiciousRegions(imageData);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
detected: tamperScore > 60,
|
|
191
|
+
score: tamperScore,
|
|
192
|
+
regions: suspiciousRegions,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Calculate tampering score
|
|
198
|
+
*/
|
|
199
|
+
private async calculateTamperScore(imageData: string | Buffer): Promise<number> {
|
|
200
|
+
// In real implementation:
|
|
201
|
+
// 1. Analyze compression artifacts
|
|
202
|
+
// 2. Check for inconsistent noise patterns
|
|
203
|
+
// 3. Detect copy-paste regions
|
|
204
|
+
// 4. Validate text rendering consistency
|
|
205
|
+
|
|
206
|
+
// Mock implementation - most documents are authentic
|
|
207
|
+
const randomScore = Math.random();
|
|
208
|
+
return randomScore < 0.95 ? Math.round(Math.random() * 30) : Math.round(60 + Math.random() * 40);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Find suspicious regions in document
|
|
213
|
+
*/
|
|
214
|
+
private async findSuspiciousRegions(imageData: string | Buffer): Promise<BoundingBox[]> {
|
|
215
|
+
// In real implementation:
|
|
216
|
+
// 1. Apply ELA and find high-error regions
|
|
217
|
+
// 2. Detect cloned regions
|
|
218
|
+
// 3. Find inconsistent JPEG blocks
|
|
219
|
+
|
|
220
|
+
// Mock implementation - usually no suspicious regions
|
|
221
|
+
const randomScore = Math.random();
|
|
222
|
+
if (randomScore < 0.95) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Return 1-2 suspicious regions
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
x: Math.round(Math.random() * 200),
|
|
230
|
+
y: Math.round(Math.random() * 100),
|
|
231
|
+
width: 50 + Math.round(Math.random() * 100),
|
|
232
|
+
height: 30 + Math.round(Math.random() * 50),
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Detect hologram on document
|
|
239
|
+
*/
|
|
240
|
+
private async detectHologram(imageData: string | Buffer): Promise<{
|
|
241
|
+
detected: boolean;
|
|
242
|
+
confidence: number;
|
|
243
|
+
} | undefined> {
|
|
244
|
+
// In real implementation:
|
|
245
|
+
// 1. Detect rainbow color patterns
|
|
246
|
+
// 2. Analyze light reflection patterns
|
|
247
|
+
// 3. Check for 3D effects
|
|
248
|
+
// 4. Validate hologram location on document
|
|
249
|
+
|
|
250
|
+
// Mock implementation
|
|
251
|
+
const randomScore = Math.random();
|
|
252
|
+
|
|
253
|
+
if (randomScore > 0.3) {
|
|
254
|
+
return {
|
|
255
|
+
detected: true,
|
|
256
|
+
confidence: 75 + Math.round(Math.random() * 20),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
detected: false,
|
|
262
|
+
confidence: Math.round(Math.random() * 50),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Calculate overall authenticity score
|
|
268
|
+
*/
|
|
269
|
+
private calculateAuthenticityScore(
|
|
270
|
+
quality: any,
|
|
271
|
+
tamper: any,
|
|
272
|
+
hologram: any
|
|
273
|
+
): number {
|
|
274
|
+
let score = 100;
|
|
275
|
+
|
|
276
|
+
// Deduct points for quality issues
|
|
277
|
+
if (quality.hasGlare) score -= 15;
|
|
278
|
+
if (quality.hasBlur) score -= 20;
|
|
279
|
+
if (quality.hasCropping) score -= 25;
|
|
280
|
+
if (quality.hasReflections) score -= 10;
|
|
281
|
+
|
|
282
|
+
// Edge score impact
|
|
283
|
+
if (quality.edgeScore < 70) score -= 15;
|
|
284
|
+
else if (quality.edgeScore < 80) score -= 10;
|
|
285
|
+
|
|
286
|
+
// Tampering severely impacts score
|
|
287
|
+
if (tamper.detected) {
|
|
288
|
+
score -= 40;
|
|
289
|
+
} else {
|
|
290
|
+
score -= Math.round(tamper.score * 0.3);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Hologram bonus (if detected and confident)
|
|
294
|
+
if (hologram?.detected && hologram.confidence > 70) {
|
|
295
|
+
score = Math.min(100, score + 5);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Math.max(0, Math.round(score));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Detect document type (passport, ID card, driver's license, etc.)
|
|
303
|
+
*/
|
|
304
|
+
async detectDocumentType(imageData: string | Buffer): Promise<string> {
|
|
305
|
+
// In real implementation:
|
|
306
|
+
// 1. Use template matching
|
|
307
|
+
// 2. Detect document layout patterns
|
|
308
|
+
// 3. OCR specific regions to identify type
|
|
309
|
+
|
|
310
|
+
// Mock implementation
|
|
311
|
+
const types = ['passport', 'national_id', 'drivers_license'];
|
|
312
|
+
return types[Math.floor(Math.random() * types.length)];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Validate document expiration
|
|
317
|
+
*/
|
|
318
|
+
validateExpiration(expirationDate: string): boolean {
|
|
319
|
+
try {
|
|
320
|
+
const expiry = new Date(expirationDate);
|
|
321
|
+
const now = new Date();
|
|
322
|
+
return expiry > now;
|
|
323
|
+
} catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Clean up resources
|
|
330
|
+
*/
|
|
331
|
+
dispose(): void {
|
|
332
|
+
this.model = null;
|
|
333
|
+
this.isModelLoaded = false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Validate document meets minimum quality standards
|
|
339
|
+
*/
|
|
340
|
+
export function meetsQualityStandards(quality: {
|
|
341
|
+
hasGlare: boolean;
|
|
342
|
+
hasBlur: boolean;
|
|
343
|
+
hasCropping: boolean;
|
|
344
|
+
hasReflections: boolean;
|
|
345
|
+
edgeScore: number;
|
|
346
|
+
}): boolean {
|
|
347
|
+
if (quality.hasBlur || quality.hasCropping) return false;
|
|
348
|
+
if (quality.hasGlare && quality.hasReflections) return false;
|
|
349
|
+
if (quality.edgeScore < 60) return false;
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|