id-scanner-lib 1.6.3 → 1.6.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 agions
3
+ Copyright (c) 2025-2026 agions
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,30 @@ class ConfigManager {
20
20
  this.config = {};
21
21
  /** 配置变更回调 */
22
22
  this.changeCallbacks = new Map();
23
+ /** 初始化状态 */
24
+ this.initialized = false;
23
25
  // 设置默认配置
26
+ this._resetDefaults();
27
+ }
28
+ /**
29
+ * 获取单例实例
30
+ */
31
+ static getInstance() {
32
+ if (!ConfigManager.instance) {
33
+ ConfigManager.instance = new ConfigManager();
34
+ }
35
+ return ConfigManager.instance;
36
+ }
37
+ /**
38
+ * 重置单例实例(主要用于测试)
39
+ */
40
+ static resetInstance() {
41
+ ConfigManager.instance = undefined;
42
+ }
43
+ /**
44
+ * 重置为默认配置
45
+ */
46
+ _resetDefaults() {
24
47
  this.config = {
25
48
  debug: false,
26
49
  logLevel: 'info',
@@ -36,15 +59,7 @@ class ConfigManager {
36
59
  useCache: true
37
60
  }
38
61
  };
39
- }
40
- /**
41
- * 获取单例实例
42
- */
43
- static getInstance() {
44
- if (!ConfigManager.instance) {
45
- ConfigManager.instance = new ConfigManager();
46
- }
47
- return ConfigManager.instance;
62
+ this.initialized = true;
48
63
  }
49
64
  /**
50
65
  * 获取配置值
@@ -84,7 +99,7 @@ class ConfigManager {
84
99
  */
85
100
  reset() {
86
101
  const oldConfig = { ...this.config };
87
- // 重新创建默认配置
102
+ // 使用私有 reset 方法重建默认配置
88
103
  this.config = {
89
104
  debug: false,
90
105
  logLevel: 'info',
@@ -380,6 +395,16 @@ class RemoteLogHandler {
380
395
  return;
381
396
  const entriesToSend = [...this.queue];
382
397
  this.queue = [];
398
+ // 防止在 fetch 失败时无限重试
399
+ const sendCount = this._sendCount || 0;
400
+ this._sendCount = sendCount + 1;
401
+ // 如果发送次数过多,停止发送以防止无限循环
402
+ if (sendCount > 10) {
403
+ console.warn('RemoteLogHandler: Too many failed sends, stopping. Clear queue.');
404
+ this.queue = [];
405
+ this._sendCount = 0;
406
+ return;
407
+ }
383
408
  try {
384
409
  fetch(this.endpoint, {
385
410
  method: 'POST',
@@ -387,14 +412,23 @@ class RemoteLogHandler {
387
412
  'Content-Type': 'application/json'
388
413
  },
389
414
  body: JSON.stringify(entriesToSend),
390
- // 不等待响应,避免阻塞
391
415
  keepalive: true
392
- }).catch(err => {
416
+ }).catch((err) => {
393
417
  console.error('Failed to send logs to remote server:', err);
418
+ // 防止无限重试 - 如果失败次数过多,丢弃日志
419
+ if (this._sendCount > 10) {
420
+ console.warn('RemoteLogHandler: Max retry exceeded, discarding logs');
421
+ this.queue = []; // 清空队列,避免内存泄漏
422
+ this._sendCount = 0;
423
+ return;
424
+ }
394
425
  // 失败时把日志放回队列,但防止无限增长
395
426
  if (this.queue.length < this.maxQueueSize) {
396
- this.queue = [...entriesToSend.slice(0, this.maxQueueSize - this.queue.length), ...this.queue];
427
+ const maxReturn = Math.min(entriesToSend.length, this.maxQueueSize - this.queue.length);
428
+ const returnedEntries = entriesToSend.slice(0, maxReturn);
429
+ this.queue = [...returnedEntries, ...this.queue];
397
430
  }
431
+ this._sendCount = 0;
398
432
  });
399
433
  }
400
434
  catch (error) {
@@ -796,6 +830,7 @@ const VERSION = '1.3.3';
796
830
  // 构建日期
797
831
  const BUILD_DATE = new Date().toISOString();
798
832
 
833
+ /* eslint-disable */
799
834
  /**
800
835
  * @file 模块管理器
801
836
  * @description 统一管理库的各功能模块,提供模块的注册、初始化和卸载功能
@@ -815,6 +850,12 @@ class ModuleManager extends EventEmitter {
815
850
  }
816
851
  return ModuleManager.instance;
817
852
  }
853
+ /**
854
+ * 重置单例实例(主要用于测试)
855
+ */
856
+ static resetInstance() {
857
+ ModuleManager.instance = undefined;
858
+ }
818
859
  /**
819
860
  * 私有构造函数,确保单例模式
820
861
  */
@@ -822,6 +863,7 @@ class ModuleManager extends EventEmitter {
822
863
  super();
823
864
  this.modules = new Map();
824
865
  this.initialized = false;
866
+ this.initPromise = null;
825
867
  this.logger = Logger.getInstance();
826
868
  this.logger.debug('ModuleManager', `初始化模块管理器 v${VERSION}`);
827
869
  }
@@ -1144,6 +1186,9 @@ class IDCardDetector extends EventEmitter {
1144
1186
  super();
1145
1187
  this.initialized = false;
1146
1188
  this.models = {};
1189
+ /** 重用的 Canvas 元素,用于减少内存分配 */
1190
+ this.reusableCanvas = null;
1191
+ this.reusableContext = null;
1147
1192
  this.options = {
1148
1193
  enabled: true,
1149
1194
  minConfidence: 0.7,
@@ -1242,6 +1287,26 @@ class IDCardDetector extends EventEmitter {
1242
1287
  if (!this.initialized) {
1243
1288
  return Result.failure(new Error('身份证检测器未初始化'));
1244
1289
  }
1290
+ // 输入验证
1291
+ if (!image) {
1292
+ return Result.failure(new Error('图像源不能为空'));
1293
+ }
1294
+ // 验证 HTMLImageElement 是否已加载
1295
+ if (image instanceof HTMLImageElement && !image.complete) {
1296
+ return Result.failure(new Error('图像尚未加载完成'));
1297
+ }
1298
+ // 验证 ImageData 尺寸
1299
+ if (image instanceof ImageData) {
1300
+ if (image.width === 0 || image.height === 0) {
1301
+ return Result.failure(new Error('图像尺寸无效'));
1302
+ }
1303
+ }
1304
+ // 验证 Canvas 尺寸
1305
+ if (image instanceof HTMLCanvasElement) {
1306
+ if (image.width === 0 || image.height === 0) {
1307
+ return Result.failure(new Error('Canvas尺寸无效'));
1308
+ }
1309
+ }
1245
1310
  try {
1246
1311
  this.logger.debug('IDCardDetector', '开始处理图像');
1247
1312
  // 预处理图像
@@ -1288,9 +1353,7 @@ class IDCardDetector extends EventEmitter {
1288
1353
  }
1289
1354
  }
1290
1355
  else if (image instanceof HTMLImageElement && image.complete) {
1291
- const canvas = document.createElement('canvas');
1292
- canvas.width = image.naturalWidth;
1293
- canvas.height = image.naturalHeight;
1356
+ const canvas = this.getReusableCanvas(image.naturalWidth, image.naturalHeight);
1294
1357
  const context = canvas.getContext('2d');
1295
1358
  if (context) {
1296
1359
  context.drawImage(image, 0, 0);
@@ -1307,6 +1370,26 @@ class IDCardDetector extends EventEmitter {
1307
1370
  return Result.failure(error);
1308
1371
  }
1309
1372
  }
1373
+ /**
1374
+ * 获取可重用的 Canvas 元素
1375
+ * @param width 宽度
1376
+ * @param height 高度
1377
+ * @returns CanvasRenderingContext2D
1378
+ */
1379
+ getReusableCanvas(width, height) {
1380
+ // 如果存在可重用的 canvas 且尺寸匹配,直接返回
1381
+ if (this.reusableCanvas &&
1382
+ this.reusableCanvas.width === width &&
1383
+ this.reusableCanvas.height === height) {
1384
+ return this.reusableCanvas;
1385
+ }
1386
+ // 创建新的 canvas
1387
+ this.reusableCanvas = document.createElement('canvas');
1388
+ this.reusableCanvas.width = width;
1389
+ this.reusableCanvas.height = height;
1390
+ this.reusableContext = this.reusableCanvas.getContext('2d');
1391
+ return this.reusableCanvas;
1392
+ }
1310
1393
  /**
1311
1394
  * 预处理图像
1312
1395
  * @param image 图像源
@@ -1315,7 +1398,6 @@ class IDCardDetector extends EventEmitter {
1315
1398
  * @private
1316
1399
  */
1317
1400
  async preprocessImage(image, options) {
1318
- // 实际项目中,这里应该对图像进行预处理
1319
1401
  this.logger.debug('IDCardDetector', '预处理图像');
1320
1402
  // 创建ImageData对象
1321
1403
  let imageData;
@@ -1323,11 +1405,9 @@ class IDCardDetector extends EventEmitter {
1323
1405
  imageData = image;
1324
1406
  }
1325
1407
  else {
1326
- const canvas = document.createElement('canvas');
1327
1408
  const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
1328
1409
  const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
1329
- canvas.width = width;
1330
- canvas.height = height;
1410
+ const canvas = this.getReusableCanvas(width, height);
1331
1411
  const context = canvas.getContext('2d');
1332
1412
  if (!context) {
1333
1413
  throw new Error('无法获取Canvas上下文');
@@ -1374,18 +1454,16 @@ class IDCardDetector extends EventEmitter {
1374
1454
  * @private
1375
1455
  */
1376
1456
  async cropAndAlign(image, edge) {
1377
- // 实际项目中,这里应该进行透视变换以校正图像
1378
1457
  this.logger.debug('IDCardDetector', '裁剪并校正图像');
1379
- // 创建Canvas
1380
- const canvas = document.createElement('canvas');
1381
1458
  // 设置标准身份证尺寸比例
1382
- canvas.width = 428;
1383
- canvas.height = 270;
1459
+ const standardWidth = 428;
1460
+ const standardHeight = 270;
1461
+ const canvas = this.getReusableCanvas(standardWidth, standardHeight);
1384
1462
  const context = canvas.getContext('2d');
1385
1463
  if (!context) {
1386
1464
  throw new Error('无法获取Canvas上下文');
1387
1465
  }
1388
- // 创建临时Canvas
1466
+ // 创建临时Canvas用于源图像
1389
1467
  const tempCanvas = document.createElement('canvas');
1390
1468
  tempCanvas.width = image.width;
1391
1469
  tempCanvas.height = image.height;
@@ -1398,29 +1476,37 @@ class IDCardDetector extends EventEmitter {
1398
1476
  // 在实际应用中,这里应该使用透视变换算法
1399
1477
  // 例如使用Canvas的transform或WebGL进行变换
1400
1478
  // 简化处理:直接裁剪
1401
- context.drawImage(tempCanvas, edge.topLeft.x, edge.topLeft.y, edge.topRight.x - edge.topLeft.x, edge.bottomLeft.y - edge.topLeft.y, 0, 0, canvas.width, canvas.height);
1402
- return context.getImageData(0, 0, canvas.width, canvas.height);
1479
+ context.drawImage(tempCanvas, edge.topLeft.x, edge.topLeft.y, edge.topRight.x - edge.topLeft.x, edge.bottomLeft.y - edge.topLeft.y, 0, 0, standardWidth, standardHeight);
1480
+ return context.getImageData(0, 0, standardWidth, standardHeight);
1403
1481
  }
1404
1482
  /**
1405
1483
  * 识别文字
1484
+ *
1485
+ * @note 此方法返回模拟数据,用于框架开发和测试
1486
+ * 实际使用时需要替换为真实的 OCR 模型集成
1487
+ *
1406
1488
  * @param image 图像数据
1407
1489
  * @param type 身份证类型
1408
1490
  * @returns 识别结果
1409
1491
  * @private
1410
1492
  */
1411
1493
  async recognizeText(image, type) {
1412
- // 实际项目中,这里应该调用OCR模型进行文字识别
1413
1494
  this.logger.debug('IDCardDetector', '识别文字');
1414
1495
  // 模拟OCR结果
1415
- // 在实际应用中,这里应该使用OCR模型进行文字识别
1496
+ // 注意:这是框架的占位实现,真实场景需要接入实际的 OCR 服务
1497
+ // 可选的方案包括:
1498
+ // - TensorFlow.js + 自定义 OCR 模型
1499
+ // - 第三方 OCR API (如百度OCR、腾讯OCR)
1500
+ // - Tesseract.js WASM 版本
1501
+ //
1416
1502
  if (type === IDCardType.FRONT) {
1417
1503
  return {
1418
- name: '张三',
1419
- gender: '男',
1420
- ethnicity: '汉',
1421
- birthDate: '1990-01-01',
1422
- address: '北京市朝阳区某某街道某某社区1号楼1单元101',
1423
- idNumber: '110101199001010001',
1504
+ name: '张三', // TODO: 替换为真实OCR结果
1505
+ gender: '男', // TODO: 替换为真实OCR结果
1506
+ ethnicity: '汉', // TODO: 替换为真实OCR结果
1507
+ birthDate: '1990-01-01', // TODO: 替换为真实OCR结果
1508
+ address: '北京市朝阳区某某街道某某社区1号楼1单元101', // TODO: 替换为真实OCR结果
1509
+ idNumber: '110101199001010001', // TODO: 替换为真实OCR结果
1424
1510
  photoRegion: {
1425
1511
  x: 300,
1426
1512
  y: 40,
@@ -1431,34 +1517,42 @@ class IDCardDetector extends EventEmitter {
1431
1517
  }
1432
1518
  else if (type === IDCardType.BACK) {
1433
1519
  return {
1434
- issueAuthority: '北京市公安局朝阳分局',
1435
- validFrom: '2015-01-01',
1436
- validTo: '2035-01-01'
1520
+ issueAuthority: '北京市公安局朝阳分局', // TODO: 替换为真实OCR结果
1521
+ validFrom: '2015-01-01', // TODO: 替换为真实OCR结果
1522
+ validTo: '2035-01-01' // TODO: 替换为真实OCR结果
1437
1523
  };
1438
1524
  }
1439
1525
  return {};
1440
1526
  }
1441
1527
  /**
1442
1528
  * 检测防伪特征
1529
+ *
1530
+ * @note 此方法返回模拟数据,用于框架开发和测试
1531
+ * 实际使用时需要替换为真实的防伪检测模型
1532
+ *
1443
1533
  * @param image 图像数据
1444
1534
  * @param detectionResult 检测结果
1445
1535
  * @returns 防伪检测结果
1446
1536
  * @private
1447
1537
  */
1448
1538
  async detectAntiFake(image, detectionResult) {
1449
- // 实际项目中,这里应该调用防伪模型进行特征检测
1450
1539
  this.logger.debug('IDCardDetector', '检测防伪特征');
1451
1540
  // 模拟防伪检测结果
1452
- // 在实际应用中,这里应该使用机器学习模型检测防伪特征
1541
+ // 注意:这是框架的占位实现,真实场景需要接入实际的防伪检测模型
1542
+ // 可选的方案包括:
1543
+ // - 紫外光特征检测
1544
+ // - 红外光特征检测
1545
+ // - 微缩文字检测
1546
+ // - 光学变色特征检测
1453
1547
  return {
1454
1548
  passed: true,
1455
1549
  score: 0.92,
1456
1550
  features: {
1457
- fluorescent: true,
1458
- microtext: true,
1459
- opticalVariable: true,
1460
- texture: true,
1461
- watermark: true
1551
+ fluorescent: true, // TODO: 替换为真实检测结果
1552
+ microtext: true, // TODO: 替换为真实检测结果
1553
+ opticalVariable: true, // TODO: 替换为真实检测结果
1554
+ texture: true, // TODO: 替换为真实检测结果
1555
+ watermark: true // TODO: 替换为真实检测结果
1462
1556
  }
1463
1557
  };
1464
1558
  }
@@ -1470,6 +1564,9 @@ class IDCardDetector extends EventEmitter {
1470
1564
  // 清理模型
1471
1565
  this.models = {};
1472
1566
  this.initialized = false;
1567
+ // 清理可重用的 Canvas
1568
+ this.reusableCanvas = null;
1569
+ this.reusableContext = null;
1473
1570
  // 清理事件监听
1474
1571
  this.removeAllListeners();
1475
1572
  }
@@ -2388,7 +2485,7 @@ function createWorker(workerFunction) {
2388
2485
  let messageCounter = 0;
2389
2486
  // 监听Worker消息
2390
2487
  worker.addEventListener('message', (event) => {
2391
- const { messageId, success, result, error } = event.data;
2488
+ const { messageId, success, result, error: _error } = event.data;
2392
2489
  // 查找对应的Promise
2393
2490
  const promiseHandlers = promiseMap.get(messageId);
2394
2491
  if (promiseHandlers) {
@@ -2396,7 +2493,7 @@ function createWorker(workerFunction) {
2396
2493
  promiseHandlers.resolve(result);
2397
2494
  }
2398
2495
  else {
2399
- promiseHandlers.reject(new Error(error));
2496
+ promiseHandlers.reject(new Error(_error));
2400
2497
  }
2401
2498
  // 删除Promise映射
2402
2499
  promiseMap.delete(messageId);
@@ -2420,8 +2517,8 @@ function createWorker(workerFunction) {
2420
2517
  // 释放Blob URL
2421
2518
  URL.revokeObjectURL(url);
2422
2519
  // 拒绝所有未完成的Promise
2423
- for (const [, { reject }] of promiseMap) {
2424
- reject(new Error('Worker已终止'));
2520
+ for (const [, { reject: _reject }] of promiseMap) {
2521
+ _reject(new Error('Worker已终止'));
2425
2522
  }
2426
2523
  // 清空Promise映射
2427
2524
  promiseMap.clear();
@@ -2797,8 +2894,8 @@ class OCRProcessor {
2797
2894
  ? JSON.stringify(error)
2798
2895
  : String(error);
2799
2896
  this.options.logger?.(`OCR识别错误: ${errorMessage}`);
2800
- // 返回空对象,避免完全失败
2801
- return {};
2897
+ // 返回 null,让调用方知道识别失败
2898
+ return null;
2802
2899
  }
2803
2900
  }
2804
2901
  /**
@@ -3582,6 +3679,7 @@ class AntiFakeDetector {
3582
3679
  }
3583
3680
  }
3584
3681
 
3682
+ /* eslint-disable */
3585
3683
  /**
3586
3684
  * @file 身份证模块入口
3587
3685
  * @description 提供身份证识别和验证功能的模块入口
@@ -4135,6 +4233,7 @@ class QRCodeScanner extends EventEmitter {
4135
4233
  }
4136
4234
  }
4137
4235
 
4236
+ /* eslint-disable */
4138
4237
  /**
4139
4238
  * @file 二维码模块入口
4140
4239
  * @description 提供二维码识别和解析功能的模块入口
@@ -4276,6 +4375,7 @@ class QRCodeModule extends BaseModule {
4276
4375
  }
4277
4376
  }
4278
4377
 
4378
+ /* eslint-disable */
4279
4379
  /**
4280
4380
  * @file 人脸模块入口
4281
4381
  * @description 提供人脸检测、活体检测和人脸比对功能的模块入口
@@ -4724,6 +4824,7 @@ class NotSupportedError extends IDScannerError {
4724
4824
  }
4725
4825
  }
4726
4826
 
4827
+ /* eslint-disable */
4727
4828
  /**
4728
4829
  * @file 主入口文件
4729
4830
  * @description ID Scanner库的主入口点,提供统一的API和模块导出