id-scanner-lib 1.5.0 → 1.6.2

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 (72) hide show
  1. package/README.md +378 -59
  2. package/dist/id-scanner-lib.esm.js +195 -10
  3. package/dist/id-scanner-lib.esm.js.map +1 -1
  4. package/dist/id-scanner-lib.js +4812 -14709
  5. package/dist/id-scanner-lib.js.map +1 -1
  6. package/dist/types/browser-image-compression.d.ts +19 -0
  7. package/dist/types/tesseract.d.ts +280 -0
  8. package/package.json +21 -11
  9. package/src/core/camera-manager.ts +16 -1
  10. package/src/core/config.ts +37 -0
  11. package/src/core/errors.ts +3 -3
  12. package/src/core/event-emitter.test.ts +42 -0
  13. package/src/core/loading-state.test.ts +67 -0
  14. package/src/core/loading-state.ts +156 -0
  15. package/src/core/logger.test.ts +49 -0
  16. package/src/core/module-manager.ts +2 -4
  17. package/src/core/scanner-factory.ts +8 -9
  18. package/src/index.ts +3 -2
  19. package/src/modules/face/face-detector.ts +123 -66
  20. package/src/modules/id-card/anti-fake-detector.ts +2 -2
  21. package/src/modules/id-card/ocr-worker.ts +1 -1
  22. package/src/modules/qrcode/qr-code-scanner.ts +2 -1
  23. package/src/modules/qrcode/types.ts +111 -7
  24. package/src/types/common.test.ts +99 -0
  25. package/src/types/common.ts +166 -0
  26. package/src/utils/camera.test.ts +30 -0
  27. package/src/utils/camera.ts +4 -1
  28. package/src/utils/error-handler.test.ts +137 -0
  29. package/src/utils/error-handler.ts +110 -0
  30. package/src/utils/index.test.ts +186 -0
  31. package/src/utils/index.ts +3 -0
  32. package/src/utils/retry.test.ts +142 -0
  33. package/src/utils/retry.ts +282 -0
  34. package/src/utils/utils.test.ts +171 -0
  35. package/src/version.ts +1 -1
  36. package/dist/types/core/base-module.d.ts +0 -44
  37. package/dist/types/core/camera-manager.d.ts +0 -258
  38. package/dist/types/core/config.d.ts +0 -88
  39. package/dist/types/core/errors.d.ts +0 -111
  40. package/dist/types/core/event-emitter.d.ts +0 -55
  41. package/dist/types/core/logger.d.ts +0 -277
  42. package/dist/types/core/module-manager.d.ts +0 -78
  43. package/dist/types/core/plugin-manager.d.ts +0 -158
  44. package/dist/types/core/resource-manager.d.ts +0 -246
  45. package/dist/types/core/result.d.ts +0 -83
  46. package/dist/types/core/scanner-factory.d.ts +0 -93
  47. package/dist/types/index.bundle.d.ts +0 -1303
  48. package/dist/types/index.d.ts +0 -86
  49. package/dist/types/interfaces/external-types.d.ts +0 -174
  50. package/dist/types/interfaces/face-detection.d.ts +0 -293
  51. package/dist/types/interfaces/scanner-module.d.ts +0 -280
  52. package/dist/types/modules/face/face-detector.d.ts +0 -170
  53. package/dist/types/modules/face/index.d.ts +0 -56
  54. package/dist/types/modules/face/liveness-detector.d.ts +0 -177
  55. package/dist/types/modules/face/types.d.ts +0 -136
  56. package/dist/types/modules/id-card/anti-fake-detector.d.ts +0 -170
  57. package/dist/types/modules/id-card/id-card-detector.d.ts +0 -131
  58. package/dist/types/modules/id-card/index.d.ts +0 -89
  59. package/dist/types/modules/id-card/ocr-processor.d.ts +0 -110
  60. package/dist/types/modules/id-card/ocr-worker.d.ts +0 -31
  61. package/dist/types/modules/id-card/types.d.ts +0 -181
  62. package/dist/types/modules/qrcode/index.d.ts +0 -51
  63. package/dist/types/modules/qrcode/qr-code-scanner.d.ts +0 -64
  64. package/dist/types/modules/qrcode/types.d.ts +0 -67
  65. package/dist/types/utils/camera.d.ts +0 -81
  66. package/dist/types/utils/image-processing.d.ts +0 -176
  67. package/dist/types/utils/index.d.ts +0 -175
  68. package/dist/types/utils/performance.d.ts +0 -81
  69. package/dist/types/utils/resource-manager.d.ts +0 -53
  70. package/dist/types/utils/types.d.ts +0 -166
  71. package/dist/types/utils/worker.d.ts +0 -52
  72. package/dist/types/version.d.ts +0 -7
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @file 重试工具
3
+ * @description 提供重试逻辑功能
4
+ * @module utils/retry
5
+ */
6
+
7
+ /**
8
+ * 重试选项
9
+ */
10
+ export interface RetryOptions {
11
+ /** 最大重试次数 */
12
+ maxAttempts?: number;
13
+ /** 初始等待时间(ms) */
14
+ initialDelay?: number;
15
+ /** 最大等待时间(ms) */
16
+ maxDelay?: number;
17
+ /** 指数退避因子 */
18
+ backoffFactor?: number;
19
+ /** 是否随机抖动 */
20
+ jitter?: boolean;
21
+ /** 重试条件 */
22
+ shouldRetry?: (error: unknown) => boolean;
23
+ }
24
+
25
+ /**
26
+ * 默认重试选项
27
+ */
28
+ const DEFAULT_OPTIONS: Required<RetryOptions> = {
29
+ maxAttempts: 3,
30
+ initialDelay: 1000,
31
+ maxDelay: 10000,
32
+ backoffFactor: 2,
33
+ jitter: true,
34
+ shouldRetry: () => true
35
+ };
36
+
37
+ /**
38
+ * 带重试的异步函数包装器
39
+ * @param fn 要执行的异步函数
40
+ * @param options 重试选项
41
+ * @returns 函数结果
42
+ */
43
+ export async function withRetry<T>(
44
+ fn: () => Promise<T>,
45
+ options: RetryOptions = {}
46
+ ): Promise<T> {
47
+ const opts = { ...DEFAULT_OPTIONS, ...options };
48
+ let lastError: unknown;
49
+ let delay = opts.initialDelay;
50
+
51
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
52
+ try {
53
+ return await fn();
54
+ } catch (error) {
55
+ lastError = error;
56
+
57
+ // 检查是否应该重试
58
+ if (!opts.shouldRetry(error)) {
59
+ throw error;
60
+ }
61
+
62
+ // 如果不是最后一次尝试,等待后重试
63
+ if (attempt < opts.maxAttempts) {
64
+ // 添加随机抖动
65
+ const jitterAmount = opts.jitter ? Math.random() * delay : 0;
66
+ const waitTime = Math.min(delay + jitterAmount, opts.maxDelay);
67
+
68
+ await sleep(waitTime);
69
+
70
+ // 指数退避
71
+ delay = Math.min(delay * opts.backoffFactor, opts.maxDelay);
72
+ }
73
+ }
74
+ }
75
+
76
+ throw lastError;
77
+ }
78
+
79
+ /**
80
+ * 睡眠函数
81
+ * @param ms 毫秒数
82
+ */
83
+ function sleep(ms: number): Promise<void> {
84
+ return new Promise(resolve => setTimeout(resolve, ms));
85
+ }
86
+
87
+ /**
88
+ * 创建指数退避重试函数
89
+ * @param options 重试选项
90
+ * @returns 包装后的重试函数
91
+ */
92
+ export function createRetryable<T extends (...args: any[]) => Promise<any>>(
93
+ fn: T,
94
+ options: RetryOptions = {}
95
+ ): T {
96
+ const wrapped = (...args: Parameters<T>): Promise<ReturnType<T>> => {
97
+ return withRetry(() => fn(...args), options);
98
+ };
99
+ Object.defineProperty(wrapped, 'name', { value: fn.name, configurable: true });
100
+ return wrapped as T;
101
+ }
102
+
103
+ /**
104
+ * 异步缓存
105
+ * @description 用于缓存异步函数的结果
106
+ */
107
+ export class AsyncCache<T> {
108
+ private cache: Map<string, { value: T; expiry: number }> = new Map();
109
+ private defaultTTL: number;
110
+
111
+ /**
112
+ * @param defaultTTL 默认过期时间(毫秒)
113
+ */
114
+ constructor(defaultTTL: number = 5 * 60 * 1000) {
115
+ this.defaultTTL = defaultTTL;
116
+ }
117
+
118
+ /**
119
+ * 获取缓存值
120
+ * @param key 缓存键
121
+ * @returns 缓存值,如果不存在或已过期则返回undefined
122
+ */
123
+ get(key: string): T | undefined {
124
+ const entry = this.cache.get(key);
125
+ if (!entry) return undefined;
126
+
127
+ if (Date.now() > entry.expiry) {
128
+ this.cache.delete(key);
129
+ return undefined;
130
+ }
131
+
132
+ return entry.value;
133
+ }
134
+
135
+ /**
136
+ * 设置缓存值
137
+ * @param key 缓存键
138
+ * @param value 缓存值
139
+ * @param ttl 过期时间(毫秒)
140
+ */
141
+ set(key: string, value: T, ttl?: number): void {
142
+ this.cache.set(key, {
143
+ value,
144
+ expiry: Date.now() + (ttl || this.defaultTTL)
145
+ });
146
+ }
147
+
148
+ /**
149
+ * 删除缓存值
150
+ * @param key 缓存键
151
+ */
152
+ delete(key: string): void {
153
+ this.cache.delete(key);
154
+ }
155
+
156
+ /**
157
+ * 清空缓存
158
+ */
159
+ clear(): void {
160
+ this.cache.clear();
161
+ }
162
+
163
+ /**
164
+ * 获取缓存大小
165
+ */
166
+ get size(): number {
167
+ // 清理过期项 (避免在迭代中删除)
168
+ const now = Date.now();
169
+ const expiredKeys: string[] = [];
170
+ for (const [key, entry] of this.cache.entries()) {
171
+ if (now > entry.expiry) {
172
+ expiredKeys.push(key);
173
+ }
174
+ }
175
+ expiredKeys.forEach(key => this.cache.delete(key));
176
+ return this.cache.size;
177
+ }
178
+
179
+ /**
180
+ * 异步获取或设置缓存
181
+ * @param key 缓存键
182
+ * @param fn 获取值的函数
183
+ * @param ttl 过期时间
184
+ * @returns 缓存值
185
+ */
186
+ async getOrSet(
187
+ key: string,
188
+ fn: () => Promise<T>,
189
+ ttl?: number
190
+ ): Promise<T> {
191
+ const cached = this.get(key);
192
+ if (cached !== undefined) {
193
+ return cached;
194
+ }
195
+
196
+ const value = await fn();
197
+ this.set(key, value, ttl);
198
+ return value;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * 简单信号量
204
+ * @description 用于控制并发数量
205
+ */
206
+ export class Semaphore {
207
+ private permits: number;
208
+ private queue: Array<() => void> = [];
209
+
210
+ /**
211
+ * @param permits 最大许可数
212
+ */
213
+ constructor(permits: number) {
214
+ this.permits = permits;
215
+ }
216
+
217
+ /**
218
+ * 获取许可
219
+ * @returns Promise
220
+ */
221
+ async acquire(): Promise<void> {
222
+ if (this.permits > 0) {
223
+ this.permits--;
224
+ return Promise.resolve();
225
+ }
226
+
227
+ return new Promise<void>(resolve => {
228
+ this.queue.push(resolve);
229
+ });
230
+ }
231
+
232
+ /**
233
+ * 释放许可
234
+ */
235
+ release(): void {
236
+ this.permits++;
237
+ const next = this.queue.shift();
238
+ if (next) {
239
+ this.permits--;
240
+ next();
241
+ }
242
+ }
243
+
244
+ /**
245
+ * 尝试获取许可
246
+ * @returns 是否获取成功
247
+ */
248
+ tryAcquire(): boolean {
249
+ if (this.permits > 0) {
250
+ this.permits--;
251
+ return true;
252
+ }
253
+ return false;
254
+ }
255
+
256
+ /**
257
+ * 获取当前可用许可数
258
+ */
259
+ get availablePermits(): number {
260
+ return this.permits;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * 带信号量的异步函数包装器
266
+ * @param fn 异步函数
267
+ * @param semaphore 信号量
268
+ * @returns 包装后的函数
269
+ */
270
+ export function withSemaphore<T extends (...args: any[]) => Promise<any>>(
271
+ fn: T,
272
+ semaphore: Semaphore
273
+ ): T {
274
+ return (async (...args: Parameters<T>): Promise<ReturnType<T>> => {
275
+ await semaphore.acquire();
276
+ try {
277
+ return await fn(...args);
278
+ } finally {
279
+ semaphore.release();
280
+ }
281
+ }) as T;
282
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @file 工具函数测试
3
+ * @description 测试通用工具函数
4
+ */
5
+
6
+ import {
7
+ delay,
8
+ throttle,
9
+ debounce,
10
+ formatBytes,
11
+ generateUUID,
12
+ chunk,
13
+ safeParseJSON,
14
+ clamp,
15
+ isValidUrl,
16
+ } from '../utils';
17
+
18
+ describe('Utils', () => {
19
+ describe('delay', () => {
20
+ it('should delay for specified milliseconds', async () => {
21
+ const start = Date.now();
22
+ await delay(100);
23
+ const elapsed = Date.now() - start;
24
+ expect(elapsed).toBeGreaterThanOrEqual(90);
25
+ });
26
+ });
27
+
28
+ describe('throttle', () => {
29
+ beforeEach(() => {
30
+ jest.useFakeTimers();
31
+ });
32
+
33
+ afterEach(() => {
34
+ jest.useRealTimers();
35
+ });
36
+
37
+ it('should throttle function calls', () => {
38
+ const fn = jest.fn();
39
+ const throttled = throttle(fn, 100);
40
+
41
+ throttled();
42
+ throttled();
43
+ throttled();
44
+
45
+ expect(fn).toHaveBeenCalledTimes(1);
46
+ });
47
+
48
+ it('should allow calls after throttle period', () => {
49
+ const fn = jest.fn();
50
+ const throttled = throttle(fn, 100);
51
+
52
+ throttled();
53
+ jest.advanceTimersByTime(100);
54
+ throttled();
55
+
56
+ expect(fn).toHaveBeenCalledTimes(2);
57
+ });
58
+ });
59
+
60
+ describe('debounce', () => {
61
+ beforeEach(() => {
62
+ jest.useFakeTimers();
63
+ });
64
+
65
+ afterEach(() => {
66
+ jest.useRealTimers();
67
+ });
68
+
69
+ it('should debounce function calls', () => {
70
+ const fn = jest.fn();
71
+ const debounced = debounce(fn, 100);
72
+
73
+ debounced();
74
+ debounced();
75
+ debounced();
76
+
77
+ jest.advanceTimersByTime(100);
78
+
79
+ expect(fn).toHaveBeenCalledTimes(1);
80
+ });
81
+
82
+ it('should execute immediately when immediate is true', () => {
83
+ const fn = jest.fn();
84
+ const debounced = debounce(fn, 100, true);
85
+
86
+ debounced();
87
+ debounced();
88
+ debounced();
89
+
90
+ expect(fn).toHaveBeenCalledTimes(1);
91
+ });
92
+ });
93
+
94
+ describe('formatBytes', () => {
95
+ it('should format bytes correctly', () => {
96
+ expect(formatBytes(0)).toBe('0 Bytes');
97
+ expect(formatBytes(1024)).toBe('1 KB');
98
+ expect(formatBytes(1024 * 1024)).toBe('1 MB');
99
+ expect(formatBytes(1024 * 1024 * 1024)).toBe('1 GB');
100
+ });
101
+
102
+ it('should respect decimal places', () => {
103
+ expect(formatBytes(1536, 2)).toBe('1.5 KB');
104
+ expect(formatBytes(1536, 0)).toBe('2 KB');
105
+ });
106
+ });
107
+
108
+ describe('generateUUID', () => {
109
+ it('should generate valid UUID format', () => {
110
+ const uuid = generateUUID();
111
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
112
+ expect(uuid).toMatch(uuidRegex);
113
+ });
114
+
115
+ it('should generate unique UUIDs', () => {
116
+ const uuids = new Set(Array.from({ length: 100 }, () => generateUUID()));
117
+ expect(uuids.size).toBe(100);
118
+ });
119
+ });
120
+
121
+ describe('chunk', () => {
122
+ it('should split array into chunks', () => {
123
+ const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
124
+ const chunks = chunk(arr, 3);
125
+ expect(chunks).toEqual([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
126
+ });
127
+
128
+ it('should handle last chunk smaller than chunk size', () => {
129
+ const arr = [1, 2, 3, 4, 5];
130
+ const chunks = chunk(arr, 2);
131
+ expect(chunks).toEqual([[1, 2], [3, 4], [5]]);
132
+ });
133
+
134
+ it('should throw error for invalid chunk size', () => {
135
+ expect(() => chunk([1, 2, 3], 0)).toThrow('Chunk size must be greater than 0');
136
+ });
137
+ });
138
+
139
+ describe('safeParseJSON', () => {
140
+ it('should parse valid JSON', () => {
141
+ const result = safeParseJSON('{"a": 1}', null);
142
+ expect(result).toEqual({ a: 1 });
143
+ });
144
+
145
+ it('should return fallback for invalid JSON', () => {
146
+ const result = safeParseJSON('invalid', { fallback: true });
147
+ expect(result).toEqual({ fallback: true });
148
+ });
149
+ });
150
+
151
+ describe('clamp', () => {
152
+ it('should clamp value within range', () => {
153
+ expect(clamp(5, 0, 10)).toBe(5);
154
+ expect(clamp(-5, 0, 10)).toBe(0);
155
+ expect(clamp(15, 0, 10)).toBe(10);
156
+ });
157
+ });
158
+
159
+ describe('isValidUrl', () => {
160
+ it('should validate correct URLs', () => {
161
+ expect(isValidUrl('https://example.com')).toBe(true);
162
+ expect(isValidUrl('http://localhost:3000')).toBe(true);
163
+ expect(isValidUrl('ftp://files.example.com')).toBe(true);
164
+ });
165
+
166
+ it('should reject invalid URLs', () => {
167
+ expect(isValidUrl('not-a-url')).toBe(false);
168
+ expect(isValidUrl('')).toBe(false);
169
+ });
170
+ });
171
+ });
package/src/version.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  // 当前版本号
8
- export const VERSION = '1.5.0';
8
+ export const VERSION = '1.3.3';
9
9
 
10
10
  // 构建日期
11
11
  export const BUILD_DATE = new Date().toISOString();
@@ -1,44 +0,0 @@
1
- /**
2
- * @file 基础模块
3
- * @description 提供基础模块实现,作为所有功能模块的基类
4
- * @module core/base-module
5
- */
6
- import { EventEmitter } from './event-emitter';
7
- import { Logger } from './logger';
8
- import { Module } from './module-manager';
9
- /**
10
- * 基础模块类
11
- * 提供模块的基本功能和生命周期管理
12
- */
13
- export declare abstract class BaseModule extends EventEmitter implements Module {
14
- /** 模块名称 */
15
- abstract readonly name: string;
16
- /** 模块版本 */
17
- readonly version: string;
18
- /** 模块是否已初始化 */
19
- protected _isInitialized: boolean;
20
- /** 日志工具 */
21
- protected logger: Logger;
22
- /**
23
- * 构造函数
24
- */
25
- constructor();
26
- /**
27
- * 获取模块是否已初始化
28
- */
29
- get isInitialized(): boolean;
30
- /**
31
- * 初始化模块
32
- * 子类必须实现此方法
33
- */
34
- abstract initialize(): Promise<void>;
35
- /**
36
- * 释放模块资源
37
- * 子类可以覆盖此方法以添加额外的资源释放逻辑
38
- */
39
- dispose(): Promise<void>;
40
- /**
41
- * 检查模块是否已初始化,如果未初始化则抛出错误
42
- */
43
- protected ensureInitialized(): void;
44
- }