id-scanner-lib 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/id-scanner-lib.esm.js +108 -315
- package/dist/id-scanner-lib.esm.js.map +1 -1
- package/dist/id-scanner-lib.js +109 -320
- package/dist/id-scanner-lib.js.map +1 -1
- package/package.json +1 -1
- package/src/compat/index.ts +7 -0
- package/src/compat/v1-adapter.ts +84 -0
- package/src/core/config.ts +113 -267
- package/src/core/errors.ts +68 -117
- package/src/core/resource-manager.ts +150 -0
- package/src/core/scanner.ts +109 -0
- package/src/core/utils/browser.ts +7 -0
- package/src/core/utils/canvas-pool.ts +171 -0
- package/src/core/utils/canvas.ts +7 -0
- package/src/core/utils/image.ts +7 -0
- package/src/core/utils/index.ts +9 -0
- package/src/core/utils/resource-manager.ts +155 -0
- package/src/core/utils/validate.ts +7 -0
- package/src/core/utils/worker.ts +130 -0
- package/src/modules/face/comparator/comparator.ts +45 -0
- package/src/modules/face/comparator/index.ts +1 -0
- package/src/modules/face/detector/detector.ts +83 -0
- package/src/modules/face/detector/index.ts +2 -0
- package/src/modules/face/detector/types.ts +80 -0
- package/src/modules/face/liveness/index.ts +7 -0
- package/src/modules/face/tracker/index.ts +7 -0
- package/src/modules/id-card/anti-fake/index.ts +7 -0
- package/src/modules/id-card/detector/index.ts +7 -0
- package/src/modules/id-card/parser/index.ts +7 -0
- package/src/modules/qr/scanner/index.ts +7 -0
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file V1 Adapter (Compatibility Layer)
|
|
3
|
+
* @description Provides backward compatibility with v1 IDScanner API
|
|
4
|
+
* @module compat/v1-adapter
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Scanner, ScannerConfig } from '../core/scanner';
|
|
8
|
+
import { EventEmitter } from '../core/event-emitter';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* V1 IDScanner configuration
|
|
12
|
+
*/
|
|
13
|
+
export interface IDScannerConfig {
|
|
14
|
+
/** Debug mode */
|
|
15
|
+
debug?: boolean;
|
|
16
|
+
/** Callback when faces are detected */
|
|
17
|
+
onFaceDetected?: (faces: any[]) => void;
|
|
18
|
+
/** Callback on error */
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* IDScanner class - provides v1 API compatibility
|
|
24
|
+
* Wraps the new Scanner class with the old IDScanner interface
|
|
25
|
+
*/
|
|
26
|
+
export class IDScanner {
|
|
27
|
+
private _scanner: Scanner;
|
|
28
|
+
private _config: IDScannerConfig;
|
|
29
|
+
private _eventEmitter: EventEmitter;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a new IDScanner instance
|
|
33
|
+
* @param config Configuration
|
|
34
|
+
*/
|
|
35
|
+
constructor(config: IDScannerConfig = {}) {
|
|
36
|
+
this._config = config;
|
|
37
|
+
this._scanner = new Scanner({ debug: config.debug });
|
|
38
|
+
this._eventEmitter = new EventEmitter();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Initialize the scanner
|
|
43
|
+
*/
|
|
44
|
+
async initialize(): Promise<void> {
|
|
45
|
+
await this._scanner.initialize();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start face recognition on a video element
|
|
50
|
+
* @param video Video element to process
|
|
51
|
+
*/
|
|
52
|
+
async startFaceRecognition(video: HTMLVideoElement): Promise<void> {
|
|
53
|
+
const faces = await this._scanner.detectFace(video);
|
|
54
|
+
if (this._config.onFaceDetected) {
|
|
55
|
+
this._config.onFaceDetected(faces);
|
|
56
|
+
}
|
|
57
|
+
this._eventEmitter.emit('faceDetected', faces);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stop the scanner and release resources
|
|
62
|
+
*/
|
|
63
|
+
async stop(): Promise<void> {
|
|
64
|
+
await this._scanner.destroy();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Register an event handler
|
|
69
|
+
* @param event Event name
|
|
70
|
+
* @param handler Event handler
|
|
71
|
+
*/
|
|
72
|
+
on(event: string, handler: (data?: any) => void): void {
|
|
73
|
+
this._eventEmitter.on(event, handler);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Unregister an event handler
|
|
78
|
+
* @param event Event name
|
|
79
|
+
* @param handler Event handler
|
|
80
|
+
*/
|
|
81
|
+
off(event: string, handler: (data?: any) => void): void {
|
|
82
|
+
this._eventEmitter.off(event, handler);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file 配置管理器
|
|
3
|
-
* @description 提供全局配置管理功能
|
|
2
|
+
* @file 配置管理器 + Scanner 配置
|
|
3
|
+
* @description 提供全局配置管理功能 + Scanner v2.0 配置接口
|
|
4
4
|
* @module core/config
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// ============================================================
|
|
8
|
+
// 原有 ConfigManager(保持向后兼容)
|
|
9
|
+
// ============================================================
|
|
10
|
+
|
|
11
11
|
export class ConfigManager {
|
|
12
|
-
/** 单例实例 */
|
|
13
12
|
private static instance: ConfigManager;
|
|
14
|
-
|
|
15
|
-
/** 配置存储 */
|
|
16
13
|
private config: Record<string, any> = {};
|
|
17
|
-
|
|
18
|
-
/** 配置变更回调 */
|
|
19
14
|
private changeCallbacks: Map<string, Array<(value: any, oldValue: any) => void>> = new Map();
|
|
20
|
-
|
|
21
|
-
/** 初始化状态 */
|
|
22
15
|
private initialized = false;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 私有构造函数
|
|
26
|
-
*/
|
|
16
|
+
|
|
27
17
|
private constructor() {
|
|
28
|
-
// 设置默认配置
|
|
29
18
|
this._resetDefaults();
|
|
30
19
|
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 获取单例实例
|
|
34
|
-
*/
|
|
20
|
+
|
|
35
21
|
public static getInstance(): ConfigManager {
|
|
36
22
|
if (!ConfigManager.instance) {
|
|
37
23
|
ConfigManager.instance = new ConfigManager();
|
|
@@ -39,285 +25,145 @@ export class ConfigManager {
|
|
|
39
25
|
return ConfigManager.instance;
|
|
40
26
|
}
|
|
41
27
|
|
|
42
|
-
/**
|
|
43
|
-
* 重置单例实例(主要用于测试)
|
|
44
|
-
*/
|
|
45
28
|
public static resetInstance(): void {
|
|
46
29
|
ConfigManager.instance = undefined as any;
|
|
47
30
|
}
|
|
48
31
|
|
|
49
|
-
/**
|
|
50
|
-
* 重置为默认配置
|
|
51
|
-
*/
|
|
52
32
|
private _resetDefaults(): void {
|
|
53
33
|
this.config = {
|
|
54
34
|
debug: false,
|
|
55
35
|
logLevel: 'info',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
36
|
+
modelPath: '/models',
|
|
37
|
+
maxRetries: 3,
|
|
38
|
+
retryDelay: 1000,
|
|
39
|
+
detectionInterval: 100,
|
|
40
|
+
face: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
confidenceThreshold: 0.5,
|
|
43
|
+
maxFaces: 10,
|
|
44
|
+
},
|
|
45
|
+
idCard: {
|
|
46
|
+
enabled: false,
|
|
47
|
+
},
|
|
48
|
+
qr: {
|
|
49
|
+
enabled: false,
|
|
63
50
|
},
|
|
64
|
-
performance: {
|
|
65
|
-
useCache: true
|
|
66
|
-
}
|
|
67
51
|
};
|
|
68
52
|
this.initialized = true;
|
|
69
53
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
get<T = any>(key: string, defaultValue?: T): T {
|
|
77
|
-
const value = this.getNestedValue(this.config, key);
|
|
78
|
-
return (value !== undefined) ? value : (defaultValue as T);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 设置配置值
|
|
83
|
-
* @param key 配置键,支持点号分隔的路径
|
|
84
|
-
* @param value 配置值
|
|
85
|
-
*/
|
|
86
|
-
set<T = any>(key: string, value: T): void {
|
|
87
|
-
const oldValue = this.get(key);
|
|
88
|
-
|
|
89
|
-
// 如果值相同,不做任何事
|
|
90
|
-
if (oldValue === value) {
|
|
91
|
-
return;
|
|
54
|
+
|
|
55
|
+
public get<T = any>(key: string, defaultValue?: T): T {
|
|
56
|
+
const keys = key.split('.');
|
|
57
|
+
let value: any = this.config;
|
|
58
|
+
for (const k of keys) {
|
|
59
|
+
value = value?.[k];
|
|
92
60
|
}
|
|
93
|
-
|
|
94
|
-
this.setNestedValue(this.config, key, value);
|
|
95
|
-
|
|
96
|
-
// 触发变更回调
|
|
97
|
-
this.triggerChangeCallbacks(key, value, oldValue);
|
|
61
|
+
return (value !== undefined ? value : defaultValue) as T;
|
|
98
62
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
reset(): void {
|
|
114
|
-
const oldConfig = { ...this.config };
|
|
115
|
-
|
|
116
|
-
// 使用私有 reset 方法重建默认配置
|
|
117
|
-
this.config = {
|
|
118
|
-
debug: false,
|
|
119
|
-
logLevel: 'info',
|
|
120
|
-
camera: {
|
|
121
|
-
resolution: {
|
|
122
|
-
width: 1280,
|
|
123
|
-
height: 720
|
|
124
|
-
},
|
|
125
|
-
frameRate: 30,
|
|
126
|
-
facingMode: 'environment'
|
|
127
|
-
},
|
|
128
|
-
performance: {
|
|
129
|
-
useCache: true
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// 触发所有回调
|
|
134
|
-
Object.keys(oldConfig).forEach(key => {
|
|
135
|
-
this.triggerChangeCallbacks(key, this.get(key), oldConfig[key]);
|
|
136
|
-
});
|
|
63
|
+
|
|
64
|
+
public set(key: string, value: any): void {
|
|
65
|
+
const keys = key.split('.');
|
|
66
|
+
let target: any = this.config;
|
|
67
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
68
|
+
if (!target[keys[i]]) target[keys[i]] = {};
|
|
69
|
+
target = target[keys[i]];
|
|
70
|
+
}
|
|
71
|
+
const oldValue = target[keys[keys.length - 1]];
|
|
72
|
+
target[keys[keys.length - 1]] = value;
|
|
73
|
+
const callbacks = this.changeCallbacks.get(key);
|
|
74
|
+
if (callbacks) {
|
|
75
|
+
callbacks.forEach(cb => cb(value, oldValue));
|
|
76
|
+
}
|
|
137
77
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
* 注册配置变更回调
|
|
141
|
-
* @param key 配置键
|
|
142
|
-
* @param callback 回调函数
|
|
143
|
-
*/
|
|
144
|
-
onConfigChange<T = any>(key: string, callback: (value: T, oldValue: T) => void): void {
|
|
78
|
+
|
|
79
|
+
public onChange(key: string, callback: (value: any, oldValue: any) => void): void {
|
|
145
80
|
if (!this.changeCallbacks.has(key)) {
|
|
146
81
|
this.changeCallbacks.set(key, []);
|
|
147
82
|
}
|
|
148
|
-
|
|
149
83
|
this.changeCallbacks.get(key)!.push(callback);
|
|
150
84
|
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* @param callback 特定回调函数,如不提供则移除所有
|
|
156
|
-
*/
|
|
157
|
-
offConfigChange<T = any>(key: string, callback?: (value: T, oldValue: T) => void): void {
|
|
158
|
-
if (!this.changeCallbacks.has(key)) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (callback) {
|
|
163
|
-
// 移除特定回调
|
|
164
|
-
const callbacks = this.changeCallbacks.get(key)!;
|
|
165
|
-
const index = callbacks.indexOf(callback as any);
|
|
166
|
-
if (index !== -1) {
|
|
167
|
-
callbacks.splice(index, 1);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 如果没有回调,删除键
|
|
171
|
-
if (callbacks.length === 0) {
|
|
172
|
-
this.changeCallbacks.delete(key);
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
// 移除所有回调
|
|
176
|
-
this.changeCallbacks.delete(key);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* 获取嵌套值
|
|
182
|
-
* @param obj 对象
|
|
183
|
-
* @param path 路径
|
|
184
|
-
*/
|
|
185
|
-
private getNestedValue(obj: Record<string, any>, path: string): any {
|
|
186
|
-
// 处理根路径
|
|
187
|
-
if (!path) {
|
|
188
|
-
return obj;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// 处理嵌套路径
|
|
192
|
-
const parts = path.split('.');
|
|
193
|
-
let current = obj;
|
|
194
|
-
|
|
195
|
-
for (const part of parts) {
|
|
196
|
-
if (current === undefined || current === null) {
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
current = current[part];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return current;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 设置嵌套值
|
|
208
|
-
* @param obj 对象
|
|
209
|
-
* @param path 路径
|
|
210
|
-
* @param value 值
|
|
211
|
-
*/
|
|
212
|
-
private setNestedValue(obj: Record<string, any>, path: string, value: any): void {
|
|
213
|
-
// 处理根路径
|
|
214
|
-
if (!path) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 处理嵌套路径
|
|
219
|
-
const parts = path.split('.');
|
|
220
|
-
let current = obj;
|
|
221
|
-
|
|
222
|
-
// 遍历路径,直到倒数第二部分
|
|
223
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
224
|
-
const part = parts[i];
|
|
225
|
-
|
|
226
|
-
// 如果不存在,创建新对象
|
|
227
|
-
if (current[part] === undefined || current[part] === null || typeof current[part] !== 'object') {
|
|
228
|
-
current[part] = {};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
current = current[part];
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// 设置最终值
|
|
235
|
-
current[parts[parts.length - 1]] = value;
|
|
85
|
+
|
|
86
|
+
/** @deprecated Use onChange instead */
|
|
87
|
+
public onConfigChange<T = any>(key: string, callback: (value: T, oldValue: T) => void): void {
|
|
88
|
+
this.onChange(key, callback);
|
|
236
89
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
*/
|
|
244
|
-
private triggerChangeCallbacks(key: string, value: any, oldValue: any): void {
|
|
245
|
-
// 触发特定键的回调
|
|
246
|
-
if (this.changeCallbacks.has(key)) {
|
|
247
|
-
const callbacks = this.changeCallbacks.get(key)!;
|
|
248
|
-
callbacks.forEach(callback => {
|
|
249
|
-
try {
|
|
250
|
-
callback(value, oldValue);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.error(`Error in config change callback for key ${key}:`, error);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// 触发父路径的回调
|
|
258
|
-
const parts = key.split('.');
|
|
259
|
-
while (parts.length > 1) {
|
|
260
|
-
parts.pop();
|
|
261
|
-
const parentKey = parts.join('.');
|
|
262
|
-
|
|
263
|
-
if (this.changeCallbacks.has(parentKey)) {
|
|
264
|
-
const parentValue = this.get(parentKey);
|
|
265
|
-
this.changeCallbacks.get(parentKey)!.forEach(callback => {
|
|
266
|
-
try {
|
|
267
|
-
callback(parentValue, parentValue);
|
|
268
|
-
} catch (error) {
|
|
269
|
-
console.error(`Error in config change callback for parent key ${parentKey}:`, error);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
|
90
|
+
|
|
91
|
+
public removeChangeCallback(key: string, callback: (value: any, oldValue: any) => void): void {
|
|
92
|
+
const callbacks = this.changeCallbacks.get(key);
|
|
93
|
+
if (callbacks) {
|
|
94
|
+
const index = callbacks.indexOf(callback);
|
|
95
|
+
if (index > -1) callbacks.splice(index, 1);
|
|
273
96
|
}
|
|
274
97
|
}
|
|
275
98
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
*/
|
|
280
|
-
public isModuleEnabled(moduleName: string): boolean {
|
|
281
|
-
const key = `modules.${moduleName}.enabled`;
|
|
282
|
-
return this.get(key) ?? false;
|
|
99
|
+
public reset(): void {
|
|
100
|
+
this.config = {};
|
|
101
|
+
this._resetDefaults();
|
|
283
102
|
}
|
|
284
103
|
}
|
|
285
104
|
|
|
105
|
+
// ============================================================
|
|
106
|
+
// Scanner v2.0 配置接口
|
|
107
|
+
// ============================================================
|
|
108
|
+
|
|
286
109
|
/**
|
|
287
|
-
*
|
|
110
|
+
* Scanner configuration interface (v2.0)
|
|
288
111
|
*/
|
|
289
|
-
export interface
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
camera: {
|
|
298
|
-
resolution: { width: number; height: number };
|
|
299
|
-
frameRate: number;
|
|
300
|
-
facingMode: string;
|
|
112
|
+
export interface ScannerConfig {
|
|
113
|
+
debug?: boolean;
|
|
114
|
+
modules?: {
|
|
115
|
+
face?: boolean;
|
|
116
|
+
faceComparator?: boolean;
|
|
117
|
+
faceLiveness?: boolean;
|
|
118
|
+
idCard?: boolean;
|
|
119
|
+
qr?: boolean;
|
|
301
120
|
};
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
121
|
+
performance?: {
|
|
122
|
+
maxCanvasWidth?: number;
|
|
123
|
+
useWorker?: boolean;
|
|
124
|
+
lazyLoad?: boolean;
|
|
305
125
|
};
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Scanner module interface
|
|
130
|
+
* All modules must implement this interface
|
|
131
|
+
*/
|
|
132
|
+
export interface ScannerModule {
|
|
133
|
+
initialize(): Promise<void>;
|
|
134
|
+
destroy(): Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Image source types
|
|
139
|
+
*/
|
|
140
|
+
export type ImageSource = HTMLVideoElement | HTMLCanvasElement | HTMLImageElement | ImageData | string;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Face detection result
|
|
144
|
+
*/
|
|
145
|
+
export interface Face {
|
|
146
|
+
box: {
|
|
147
|
+
x: number;
|
|
148
|
+
y: number;
|
|
149
|
+
width: number;
|
|
150
|
+
height: number;
|
|
151
|
+
};
|
|
152
|
+
confidence: number;
|
|
153
|
+
keypoints?: {
|
|
154
|
+
leftEye?: { x: number; y: number };
|
|
155
|
+
rightEye?: { x: number; y: number };
|
|
156
|
+
nose?: { x: number; y: number };
|
|
157
|
+
leftMouth?: { x: number; y: number };
|
|
158
|
+
rightMouth?: { x: number; y: number };
|
|
311
159
|
};
|
|
312
|
-
|
|
160
|
+
embedding?: number[];
|
|
161
|
+
}
|
|
313
162
|
|
|
314
163
|
/**
|
|
315
|
-
*
|
|
316
|
-
* 定义所有模块共享的基础配置属性
|
|
164
|
+
* Module configuration
|
|
317
165
|
*/
|
|
318
166
|
export interface ModuleConfig {
|
|
319
|
-
|
|
320
|
-
enabled: boolean;
|
|
321
|
-
/** 其他模块特定配置 */
|
|
167
|
+
enabled?: boolean;
|
|
322
168
|
[key: string]: any;
|
|
323
|
-
}
|
|
169
|
+
}
|