id-scanner-lib 1.6.7 → 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.
Files changed (48) hide show
  1. package/dist/id-scanner-lib.esm.js +994 -1139
  2. package/dist/id-scanner-lib.esm.js.map +1 -1
  3. package/dist/id-scanner-lib.js +995 -1144
  4. package/dist/id-scanner-lib.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/compat/index.ts +7 -0
  7. package/src/compat/v1-adapter.ts +84 -0
  8. package/src/core/camera-manager.ts +43 -76
  9. package/src/core/camera-stream-manager.ts +318 -0
  10. package/src/core/config.ts +113 -267
  11. package/src/core/errors.ts +68 -117
  12. package/src/core/logger.ts +158 -81
  13. package/src/core/resource-manager.ts +150 -0
  14. package/src/core/scanner.ts +109 -0
  15. package/src/core/utils/browser.ts +7 -0
  16. package/src/core/utils/canvas-pool.ts +171 -0
  17. package/src/core/utils/canvas.ts +7 -0
  18. package/src/core/utils/image.ts +7 -0
  19. package/src/core/utils/index.ts +9 -0
  20. package/src/core/utils/resource-manager.ts +155 -0
  21. package/src/core/utils/validate.ts +7 -0
  22. package/src/core/utils/worker.ts +130 -0
  23. package/src/modules/face/comparator/comparator.ts +45 -0
  24. package/src/modules/face/comparator/index.ts +1 -0
  25. package/src/modules/face/detector/detector.ts +83 -0
  26. package/src/modules/face/detector/index.ts +2 -0
  27. package/src/modules/face/detector/types.ts +80 -0
  28. package/src/modules/face/face-comparator.ts +150 -0
  29. package/src/modules/face/face-detector-options.ts +104 -0
  30. package/src/modules/face/face-detector.ts +121 -376
  31. package/src/modules/face/face-detector.ts.bak +991 -0
  32. package/src/modules/face/face-model-loader.ts +222 -0
  33. package/src/modules/face/face-result-converter.ts +225 -0
  34. package/src/modules/face/face-tracker.ts +207 -0
  35. package/src/modules/face/liveness/index.ts +7 -0
  36. package/src/modules/face/liveness-detector.ts +2 -2
  37. package/src/modules/face/tracker/index.ts +7 -0
  38. package/src/modules/id-card/anti-fake/index.ts +7 -0
  39. package/src/modules/id-card/detector/index.ts +7 -0
  40. package/src/modules/id-card/id-card-text-parser.ts +151 -0
  41. package/src/modules/id-card/ocr-processor.ts +20 -257
  42. package/src/modules/id-card/ocr-worker.ts +2 -183
  43. package/src/modules/id-card/parser/index.ts +7 -0
  44. package/src/modules/qr/scanner/index.ts +7 -0
  45. package/src/utils/canvas-pool.ts +273 -0
  46. package/src/utils/edge-detector.ts +232 -0
  47. package/src/utils/image-processing.ts +92 -419
  48. package/src/utils/index.ts +1 -0
@@ -62,19 +62,22 @@ export class ConsoleLogHandler implements LogHandler {
62
62
  handle(entry: LogEntry): void {
63
63
  const timestamp = new Date(entry.timestamp).toISOString();
64
64
  const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`;
65
-
65
+
66
+ // 优先使用 error,其次使用 data
67
+ const extra = entry.error || entry.data || '';
68
+
66
69
  switch (entry.level) {
67
70
  case LoggerLevel.DEBUG:
68
- console.debug(prefix, entry.message, entry.error || '');
71
+ console.debug(prefix, entry.message, extra);
69
72
  break;
70
73
  case LoggerLevel.INFO:
71
- console.info(prefix, entry.message, entry.error || '');
74
+ console.info(prefix, entry.message, extra);
72
75
  break;
73
76
  case LoggerLevel.WARN:
74
- console.warn(prefix, entry.message, entry.error || '');
77
+ console.warn(prefix, entry.message, extra);
75
78
  break;
76
79
  case LoggerLevel.ERROR:
77
- console.error(prefix, entry.message, entry.error || '');
80
+ console.error(prefix, entry.message, extra);
78
81
  break;
79
82
  default:
80
83
  // 输出什么也不做
@@ -158,10 +161,14 @@ export class RemoteLogHandler implements LogHandler {
158
161
  /** 发送间隔(毫秒) */
159
162
  private flushInterval: number;
160
163
  /** 定时发送的计时器ID */
161
- private timerId: number | null = null;
164
+ private timerId: ReturnType<typeof setInterval> | null = null;
162
165
  /** 是否在浏览器环境 */
163
166
  private readonly isBrowser: boolean;
164
-
167
+ /** 最大连续失败次数,超过则丢弃日志 */
168
+ private readonly maxConsecutiveFailures: number;
169
+ /** 当前连续失败计数 */
170
+ private consecutiveFailures: number = 0;
171
+
165
172
  /**
166
173
  * 构造函数
167
174
  * @param endpoint 远程服务器URL
@@ -173,10 +180,11 @@ export class RemoteLogHandler implements LogHandler {
173
180
  this.maxQueueSize = maxQueueSize;
174
181
  this.flushInterval = flushInterval;
175
182
  this.isBrowser = typeof window !== 'undefined' && typeof window.addEventListener === 'function';
176
-
183
+ this.maxConsecutiveFailures = 10;
184
+
177
185
  // 设置定时发送
178
186
  this.startTimer();
179
-
187
+
180
188
  // 页面卸载前尝试发送剩余日志
181
189
  if (this.isBrowser) {
182
190
  window.addEventListener('beforeunload', () => {
@@ -184,7 +192,7 @@ export class RemoteLogHandler implements LogHandler {
184
192
  });
185
193
  }
186
194
  }
187
-
195
+
188
196
  /**
189
197
  * 处理日志条目
190
198
  * @param entry 日志条目
@@ -193,86 +201,96 @@ export class RemoteLogHandler implements LogHandler {
193
201
  // 只处理INFO以上级别的日志
194
202
  if (entry.level >= LoggerLevel.INFO) {
195
203
  this.queue.push(entry);
196
-
204
+
197
205
  // 如果队列满了,立即发送
198
206
  if (this.queue.length >= this.maxQueueSize) {
199
207
  this.flush();
200
208
  }
201
209
  }
202
210
  }
203
-
211
+
204
212
  /**
205
213
  * 发送队列中的日志
206
214
  */
207
215
  flush(): void {
208
216
  if (this.queue.length === 0) return;
209
-
210
- const entriesToSend = [...this.queue];
211
- this.queue = [];
212
-
213
- // 防止在 fetch 失败时无限重试
214
- const sendCount = (this as any)._sendCount || 0;
215
- (this as any)._sendCount = sendCount + 1;
216
-
217
- // 如果发送次数过多,停止发送以防止无限循环
218
- if (sendCount > 10) {
219
- console.warn('RemoteLogHandler: Too many failed sends, stopping. Clear queue.');
217
+
218
+ // 如果连续失败次数过多,停止发送以防止无限循环
219
+ if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
220
+ console.warn('RemoteLogHandler: Too many consecutive failures, stopping. Clear queue.');
220
221
  this.queue = [];
221
- (this as any)._sendCount = 0;
222
+ this.consecutiveFailures = 0;
222
223
  return;
223
224
  }
224
-
225
- try {
226
- fetch(this.endpoint, {
227
- method: 'POST',
228
- headers: {
229
- 'Content-Type': 'application/json'
230
- },
231
- body: JSON.stringify(entriesToSend),
232
- keepalive: true
233
- }).catch((err: Error) => {
234
- console.error('Failed to send logs to remote server:', err);
235
-
236
- // 防止无限重试 - 如果失败次数过多,丢弃日志
237
- if ((this as any)._sendCount > 10) {
238
- console.warn('RemoteLogHandler: Max retry exceeded, discarding logs');
239
- this.queue = []; // 清空队列,避免内存泄漏
240
- (this as any)._sendCount = 0;
241
- return;
242
- }
243
-
244
- // 失败时把日志放回队列,但防止无限增长
245
- if (this.queue.length < this.maxQueueSize) {
246
- const maxReturn = Math.min(entriesToSend.length, this.maxQueueSize - this.queue.length);
247
- const returnedEntries = entriesToSend.slice(0, maxReturn);
248
- this.queue = [...returnedEntries, ...this.queue];
249
- }
250
-
251
- (this as any)._sendCount = 0;
252
- });
253
- } catch (error) {
254
- console.error('Error sending logs:', error);
255
- }
225
+
226
+ const entriesToSend = [...this.queue];
227
+ this.queue = [];
228
+
229
+ this.sendLogEntries(entriesToSend);
256
230
  }
257
-
231
+
232
+ /**
233
+ * 发送日志条目到远程服务器
234
+ * @param entries 日志条目数组
235
+ */
236
+ private sendLogEntries(entries: LogEntry[]): void {
237
+ if (entries.length === 0) return;
238
+
239
+ const controller = new AbortController();
240
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s 超时
241
+
242
+ fetch(this.endpoint, {
243
+ method: 'POST',
244
+ headers: {
245
+ 'Content-Type': 'application/json'
246
+ },
247
+ body: JSON.stringify(entries),
248
+ keepalive: true,
249
+ signal: controller.signal
250
+ }).then(() => {
251
+ clearTimeout(timeoutId);
252
+ this.consecutiveFailures = 0; // 发送成功,重置失败计数
253
+ }).catch((err: Error) => {
254
+ clearTimeout(timeoutId);
255
+ console.error('Failed to send logs to remote server:', err);
256
+
257
+ this.consecutiveFailures++;
258
+
259
+ // 如果失败次数过多,丢弃日志防止内存泄漏
260
+ if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
261
+ console.warn('RemoteLogHandler: Max consecutive failures exceeded, discarding logs');
262
+ this.queue = [];
263
+ this.consecutiveFailures = 0;
264
+ return;
265
+ }
266
+
267
+ // 失败时把日志放回队列,但防止无限增长
268
+ const maxReturn = Math.min(entries.length, this.maxQueueSize - this.queue.length);
269
+ if (maxReturn > 0) {
270
+ const returnedEntries = entries.slice(0, maxReturn);
271
+ this.queue = [...returnedEntries, ...this.queue];
272
+ }
273
+ });
274
+ }
275
+
258
276
  /**
259
277
  * 开始定时发送
260
278
  */
261
279
  startTimer(): void {
262
280
  if (!this.isBrowser) return;
263
281
  if (this.timerId !== null) return;
264
-
265
- this.timerId = window.setInterval(() => {
282
+
283
+ this.timerId = setInterval(() => {
266
284
  this.flush();
267
285
  }, this.flushInterval);
268
286
  }
269
-
287
+
270
288
  /**
271
289
  * 停止定时发送
272
290
  */
273
291
  stopTimer(): void {
274
292
  if (this.timerId !== null) {
275
- window.clearInterval(this.timerId);
293
+ clearInterval(this.timerId);
276
294
  this.timerId = null;
277
295
  }
278
296
  }
@@ -364,40 +382,56 @@ export class Logger {
364
382
  * 记录调试级别日志
365
383
  * @param tag 标签
366
384
  * @param message 消息
367
- * @param error 错误
385
+ * @param errorOrData 错误对象或结构化数据
368
386
  */
369
- debug(tag: string, message: string, error?: Error): void {
370
- this.log(LoggerLevel.DEBUG, tag, message, error);
387
+ debug(tag: string, message: string, errorOrData?: Error | object): void {
388
+ if (errorOrData instanceof Error) {
389
+ this.log(LoggerLevel.DEBUG, tag, message, errorOrData);
390
+ } else {
391
+ this.logWithData(LoggerLevel.DEBUG, tag, message, errorOrData);
392
+ }
371
393
  }
372
-
394
+
373
395
  /**
374
396
  * 记录信息级别日志
375
397
  * @param tag 标签
376
398
  * @param message 消息
377
- * @param error 错误
399
+ * @param errorOrData 错误对象或结构化数据
378
400
  */
379
- info(tag: string, message: string, error?: Error): void {
380
- this.log(LoggerLevel.INFO, tag, message, error);
401
+ info(tag: string, message: string, errorOrData?: Error | object): void {
402
+ if (errorOrData instanceof Error) {
403
+ this.log(LoggerLevel.INFO, tag, message, errorOrData);
404
+ } else {
405
+ this.logWithData(LoggerLevel.INFO, tag, message, errorOrData);
406
+ }
381
407
  }
382
-
408
+
383
409
  /**
384
410
  * 记录警告级别日志
385
411
  * @param tag 标签
386
412
  * @param message 消息
387
- * @param error 错误
413
+ * @param errorOrData 错误对象或结构化数据
388
414
  */
389
- warn(tag: string, message: string, error?: Error): void {
390
- this.log(LoggerLevel.WARN, tag, message, error);
415
+ warn(tag: string, message: string, errorOrData?: Error | object): void {
416
+ if (errorOrData instanceof Error) {
417
+ this.log(LoggerLevel.WARN, tag, message, errorOrData);
418
+ } else {
419
+ this.logWithData(LoggerLevel.WARN, tag, message, errorOrData);
420
+ }
391
421
  }
392
-
422
+
393
423
  /**
394
424
  * 记录错误级别日志
395
425
  * @param tag 标签
396
426
  * @param message 消息
397
- * @param error 错误
427
+ * @param errorOrData 错误对象或结构化数据
398
428
  */
399
- error(tag: string, message: string, error?: Error): void {
400
- this.log(LoggerLevel.ERROR, tag, message, error);
429
+ error(tag: string, message: string, errorOrData?: Error | object): void {
430
+ if (errorOrData instanceof Error) {
431
+ this.log(LoggerLevel.ERROR, tag, message, errorOrData);
432
+ } else {
433
+ this.logWithData(LoggerLevel.ERROR, tag, message, errorOrData);
434
+ }
401
435
  }
402
436
 
403
437
  /**
@@ -448,6 +482,46 @@ export class Logger {
448
482
  }
449
483
  }
450
484
 
485
+ /**
486
+ * 记录日志(支持结构化数据)
487
+ * @param level 日志级别
488
+ * @param tag 标签
489
+ * @param message 消息
490
+ * @param data 结构化数据
491
+ */
492
+ private logWithData(level: LoggerLevel, tag: string, message: string, data?: object): void {
493
+ // 检查日志级别
494
+ const levelValue = this.getLevelValue(level);
495
+ const currentLevelValue = this.getLevelValue(this.logLevel);
496
+
497
+ if (levelValue < currentLevelValue) {
498
+ return;
499
+ }
500
+
501
+ // 创建日志条目
502
+ const entry: LogEntry = {
503
+ timestamp: Date.now(),
504
+ level: level,
505
+ tag: tag || this.defaultTag,
506
+ message,
507
+ data
508
+ };
509
+
510
+ // 分发到所有处理程序
511
+ for (const handler of this.handlers) {
512
+ try {
513
+ handler.handle(entry);
514
+ } catch (handlerError) {
515
+ console.error(`[Logger] 处理程序错误:`, handlerError);
516
+ }
517
+ }
518
+
519
+ // 如果没有处理程序,使用控制台
520
+ if (this.handlers.length === 0) {
521
+ this.consoleOutput(entry);
522
+ }
523
+ }
524
+
451
525
  /**
452
526
  * 控制台输出
453
527
  * @param entry 日志条目
@@ -455,19 +529,22 @@ export class Logger {
455
529
  private consoleOutput(entry: LogEntry): void {
456
530
  const timestamp = new Date(entry.timestamp).toISOString();
457
531
  const prefix = `[${timestamp}] [${entry.level.toUpperCase()}] [${entry.tag}]`;
458
-
532
+
533
+ // 构造日志内容:错误对象或数据对象
534
+ const extra = entry.error || entry.data || '';
535
+
459
536
  switch (entry.level) {
460
537
  case LoggerLevel.DEBUG:
461
- console.debug(`${prefix} ${entry.message}`, entry.error || '');
538
+ console.debug(`${prefix} ${entry.message}`, extra);
462
539
  break;
463
540
  case LoggerLevel.INFO:
464
- console.info(`${prefix} ${entry.message}`, entry.error || '');
541
+ console.info(`${prefix} ${entry.message}`, extra);
465
542
  break;
466
543
  case LoggerLevel.WARN:
467
- console.warn(`${prefix} ${entry.message}`, entry.error || '');
544
+ console.warn(`${prefix} ${entry.message}`, extra);
468
545
  break;
469
546
  case LoggerLevel.ERROR:
470
- console.error(`${prefix} ${entry.message}`, entry.error || '');
547
+ console.error(`${prefix} ${entry.message}`, extra);
471
548
  break;
472
549
  }
473
550
  }
@@ -10,6 +10,156 @@ import { ResourceLoadError } from './errors';
10
10
  import { EventEmitter } from './event-emitter';
11
11
  import { Result } from './result';
12
12
 
13
+ /**
14
+ * 模型分片信息
15
+ */
16
+ export interface ModelShard {
17
+ name: string; // e.g. 'face-detector'
18
+ version: string; // e.g. '1.0.0'
19
+ url: string; // 完整 URL
20
+ size: number; // bytes
21
+ hash?: string; // 完整性校验
22
+ }
23
+
24
+ /**
25
+ * 模型缓存信息
26
+ */
27
+ export interface ModelCacheInfo {
28
+ name: string;
29
+ version: string;
30
+ cachedAt: number;
31
+ size: number;
32
+ }
33
+
34
+ /**
35
+ * IndexedDB 模型缓存管理器
36
+ * 提供分片模型的持久化缓存功能
37
+ */
38
+ export class ModelCacheManager {
39
+ private _dbName = 'id-scanner-lib-models';
40
+ private _storeName = 'model-cache';
41
+ private _db: IDBDatabase | null = null;
42
+
43
+ /**
44
+ * 初始化缓存管理器(打开 IndexedDB)
45
+ */
46
+ async init(): Promise<void> {
47
+ return new Promise((resolve, reject) => {
48
+ const request = indexedDB.open(this._dbName, 1);
49
+ request.onerror = () => reject(new Error('Failed to open IndexedDB'));
50
+ request.onsuccess = () => {
51
+ this._db = request.result;
52
+ resolve();
53
+ };
54
+ request.onupgradeneeded = (event) => {
55
+ const db = (event.target as IDBOpenDBRequest).result;
56
+ if (!db.objectStoreNames.contains(this._storeName)) {
57
+ const store = db.createObjectStore(this._storeName, { keyPath: 'key' });
58
+ store.createIndex('name', 'name', { unique: false });
59
+ store.createIndex('version', 'version', { unique: false });
60
+ }
61
+ };
62
+ });
63
+ }
64
+
65
+ /**
66
+ * 生成缓存键
67
+ */
68
+ private _makeKey(model: ModelShard): string {
69
+ return `${model.name}@${model.version}`;
70
+ }
71
+
72
+ /**
73
+ * 检查模型是否已缓存
74
+ */
75
+ async has(model: ModelShard): Promise<boolean> {
76
+ if (!this._db) return false;
77
+ return new Promise((resolve, reject) => {
78
+ const tx = this._db!.transaction(this._storeName, 'readonly');
79
+ const store = tx.objectStore(this._storeName);
80
+ const req = store.get(this._makeKey(model));
81
+ req.onsuccess = () => resolve(!!req.result);
82
+ req.onerror = () => reject(req.error);
83
+ });
84
+ }
85
+
86
+ /**
87
+ * 存储模型数据
88
+ */
89
+ async store(model: ModelShard, data: ArrayBuffer): Promise<void> {
90
+ if (!this._db) throw new Error('ModelCacheManager not initialized');
91
+ return new Promise((resolve, reject) => {
92
+ const tx = this._db!.transaction(this._storeName, 'readwrite');
93
+ const store = tx.objectStore(this._storeName);
94
+ const record = {
95
+ key: this._makeKey(model),
96
+ name: model.name,
97
+ version: model.version,
98
+ url: model.url,
99
+ size: model.size,
100
+ data,
101
+ cachedAt: Date.now(),
102
+ };
103
+ const req = store.put(record);
104
+ req.onsuccess = () => resolve();
105
+ req.onerror = () => reject(req.error);
106
+ });
107
+ }
108
+
109
+ /**
110
+ * 加载模型数据
111
+ */
112
+ async load(model: ModelShard): Promise<ArrayBuffer | null> {
113
+ if (!this._db) throw new Error('ModelCacheManager not initialized');
114
+ return new Promise((resolve, reject) => {
115
+ const tx = this._db!.transaction(this._storeName, 'readonly');
116
+ const store = tx.objectStore(this._storeName);
117
+ const req = store.get(this._makeKey(model));
118
+ req.onsuccess = () => resolve(req.result ? req.result.data : null);
119
+ req.onerror = () => reject(req.error);
120
+ });
121
+ }
122
+
123
+ /**
124
+ * 清除所有缓存
125
+ */
126
+ async clear(): Promise<void> {
127
+ if (!this._db) return;
128
+ return new Promise((resolve, reject) => {
129
+ const tx = this._db!.transaction(this._storeName, 'readwrite');
130
+ const store = tx.objectStore(this._storeName);
131
+ const req = store.clear();
132
+ req.onsuccess = () => resolve();
133
+ req.onerror = () => reject(req.error);
134
+ });
135
+ }
136
+
137
+ /**
138
+ * 列出已缓存模型
139
+ */
140
+ async listCached(): Promise<ModelCacheInfo[]> {
141
+ if (!this._db) return [];
142
+ return new Promise((resolve, reject) => {
143
+ const tx = this._db!.transaction(this._storeName, 'readonly');
144
+ const store = tx.objectStore(this._storeName);
145
+ const req = store.getAll();
146
+ req.onsuccess = () => {
147
+ const results: ModelCacheInfo[] = req.result.map((r: any) => ({
148
+ name: r.name,
149
+ version: r.version,
150
+ cachedAt: r.cachedAt,
151
+ size: r.size,
152
+ }));
153
+ resolve(results);
154
+ };
155
+ req.onerror = () => reject(req.error);
156
+ });
157
+ }
158
+ }
159
+
160
+ /** 全局模型缓存管理器实例 */
161
+ export const modelCache = new ModelCacheManager();
162
+
13
163
  /**
14
164
  * 资源类型枚举
15
165
  */
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @file Scanner Core Class
3
+ * @description Main entry point for the Scanner library
4
+ * @module core/scanner
5
+ */
6
+
7
+ import { ScannerConfig, ScannerModule, ImageSource, Face } from './config';
8
+ import { ScannerError, ErrorCodes } from './errors';
9
+
10
+ /**
11
+ * Scanner main class
12
+ * Provides unified interface for all scanning operations
13
+ */
14
+ export class Scanner {
15
+ /**
16
+ * Internal configuration
17
+ */
18
+ private _config: Required<ScannerConfig>;
19
+ /**
20
+ * Loaded modules
21
+ */
22
+ private _modules: Map<string, ScannerModule>;
23
+ /**
24
+ * Initialization state
25
+ */
26
+ private _initialized: boolean = false;
27
+
28
+ /**
29
+ * Create a new Scanner instance
30
+ * @param config Scanner configuration
31
+ */
32
+ constructor(config: ScannerConfig = {}) {
33
+ this._config = this._normalizeConfig(config);
34
+ this._modules = new Map();
35
+ }
36
+
37
+ /**
38
+ * Check if scanner is initialized
39
+ */
40
+ get initialized(): boolean {
41
+ return this._initialized;
42
+ }
43
+
44
+ /**
45
+ * Normalize configuration with defaults
46
+ * @param config User provided config
47
+ * @returns Normalized config with defaults
48
+ */
49
+ private _normalizeConfig(config: ScannerConfig): Required<ScannerConfig> {
50
+ return {
51
+ debug: config.debug ?? false,
52
+ modules: {
53
+ face: config.modules?.face ?? true,
54
+ faceComparator: config.modules?.faceComparator ?? false,
55
+ faceLiveness: config.modules?.faceLiveness ?? false,
56
+ idCard: config.modules?.idCard ?? false,
57
+ qr: config.modules?.qr ?? false,
58
+ },
59
+ performance: {
60
+ maxCanvasWidth: config.performance?.maxCanvasWidth ?? 1280,
61
+ useWorker: config.performance?.useWorker ?? true,
62
+ lazyLoad: config.performance?.lazyLoad ?? true,
63
+ },
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Initialize the scanner and load enabled modules
69
+ */
70
+ async initialize(): Promise<void> {
71
+ // Immediately load face core module
72
+ const { FaceDetector } = await import('../modules/face/detector');
73
+ const detector = new FaceDetector();
74
+ await detector.initialize();
75
+ this._modules.set('face-detector', detector);
76
+ this._initialized = true;
77
+ }
78
+
79
+ /**
80
+ * Detect faces in an image source
81
+ * @param input Image source (video, canvas, image, etc.)
82
+ * @returns Array of detected faces
83
+ */
84
+ async detectFace(input: ImageSource): Promise<Face[]> {
85
+ const detector = this._modules.get('face-detector');
86
+ if (!detector) {
87
+ throw new ScannerError(
88
+ 'Scanner not initialized. Call initialize() first.',
89
+ ErrorCodes.NOT_INITIALIZED
90
+ );
91
+ }
92
+ return (detector as any).detect(input);
93
+ }
94
+
95
+ /**
96
+ * Destroy the scanner and release all resources
97
+ */
98
+ async destroy(): Promise<void> {
99
+ for (const module of this._modules.values()) {
100
+ await module.destroy();
101
+ }
102
+ this._modules.clear();
103
+ this._initialized = false;
104
+ }
105
+ }
106
+
107
+ // Re-export types
108
+ export { ScannerConfig, ScannerModule, ImageSource, Face } from './config';
109
+ export { ScannerError, ErrorCodes } from './errors';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Browser Utilities
3
+ * @description Browser environment utility functions
4
+ * @module core/utils/browser
5
+ */
6
+
7
+ export {};