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.
- package/README.md +378 -59
- package/dist/id-scanner-lib.esm.js +195 -10
- package/dist/id-scanner-lib.esm.js.map +1 -1
- package/dist/id-scanner-lib.js +4812 -14709
- package/dist/id-scanner-lib.js.map +1 -1
- package/dist/types/browser-image-compression.d.ts +19 -0
- package/dist/types/tesseract.d.ts +280 -0
- package/package.json +21 -11
- package/src/core/camera-manager.ts +16 -1
- package/src/core/config.ts +37 -0
- package/src/core/errors.ts +3 -3
- package/src/core/event-emitter.test.ts +42 -0
- package/src/core/loading-state.test.ts +67 -0
- package/src/core/loading-state.ts +156 -0
- package/src/core/logger.test.ts +49 -0
- package/src/core/module-manager.ts +2 -4
- package/src/core/scanner-factory.ts +8 -9
- package/src/index.ts +3 -2
- package/src/modules/face/face-detector.ts +123 -66
- package/src/modules/id-card/anti-fake-detector.ts +2 -2
- package/src/modules/id-card/ocr-worker.ts +1 -1
- package/src/modules/qrcode/qr-code-scanner.ts +2 -1
- package/src/modules/qrcode/types.ts +111 -7
- package/src/types/common.test.ts +99 -0
- package/src/types/common.ts +166 -0
- package/src/utils/camera.test.ts +30 -0
- package/src/utils/camera.ts +4 -1
- package/src/utils/error-handler.test.ts +137 -0
- package/src/utils/error-handler.ts +110 -0
- package/src/utils/index.test.ts +186 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/retry.test.ts +142 -0
- package/src/utils/retry.ts +282 -0
- package/src/utils/utils.test.ts +171 -0
- package/src/version.ts +1 -1
- package/dist/types/core/base-module.d.ts +0 -44
- package/dist/types/core/camera-manager.d.ts +0 -258
- package/dist/types/core/config.d.ts +0 -88
- package/dist/types/core/errors.d.ts +0 -111
- package/dist/types/core/event-emitter.d.ts +0 -55
- package/dist/types/core/logger.d.ts +0 -277
- package/dist/types/core/module-manager.d.ts +0 -78
- package/dist/types/core/plugin-manager.d.ts +0 -158
- package/dist/types/core/resource-manager.d.ts +0 -246
- package/dist/types/core/result.d.ts +0 -83
- package/dist/types/core/scanner-factory.d.ts +0 -93
- package/dist/types/index.bundle.d.ts +0 -1303
- package/dist/types/index.d.ts +0 -86
- package/dist/types/interfaces/external-types.d.ts +0 -174
- package/dist/types/interfaces/face-detection.d.ts +0 -293
- package/dist/types/interfaces/scanner-module.d.ts +0 -280
- package/dist/types/modules/face/face-detector.d.ts +0 -170
- package/dist/types/modules/face/index.d.ts +0 -56
- package/dist/types/modules/face/liveness-detector.d.ts +0 -177
- package/dist/types/modules/face/types.d.ts +0 -136
- package/dist/types/modules/id-card/anti-fake-detector.d.ts +0 -170
- package/dist/types/modules/id-card/id-card-detector.d.ts +0 -131
- package/dist/types/modules/id-card/index.d.ts +0 -89
- package/dist/types/modules/id-card/ocr-processor.d.ts +0 -110
- package/dist/types/modules/id-card/ocr-worker.d.ts +0 -31
- package/dist/types/modules/id-card/types.d.ts +0 -181
- package/dist/types/modules/qrcode/index.d.ts +0 -51
- package/dist/types/modules/qrcode/qr-code-scanner.d.ts +0 -64
- package/dist/types/modules/qrcode/types.d.ts +0 -67
- package/dist/types/utils/camera.d.ts +0 -81
- package/dist/types/utils/image-processing.d.ts +0 -176
- package/dist/types/utils/index.d.ts +0 -175
- package/dist/types/utils/performance.d.ts +0 -81
- package/dist/types/utils/resource-manager.d.ts +0 -53
- package/dist/types/utils/types.d.ts +0 -166
- package/dist/types/utils/worker.d.ts +0 -52
- package/dist/types/version.d.ts +0 -7
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 统一错误处理工具
|
|
3
|
+
* @description 提供统一的错误处理和日志记录功能
|
|
4
|
+
* @module utils/error-handler
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Logger } from '../core/logger';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 错误严重级别
|
|
11
|
+
*/
|
|
12
|
+
export enum ErrorSeverity {
|
|
13
|
+
DEBUG = 'debug',
|
|
14
|
+
INFO = 'info',
|
|
15
|
+
WARN = 'warn',
|
|
16
|
+
ERROR = 'error'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 错误处理工具类
|
|
21
|
+
*/
|
|
22
|
+
export class ErrorHandler {
|
|
23
|
+
private static logger = Logger.getInstance();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 处理错误并记录日志
|
|
27
|
+
* @param context 错误上下文(模块名)
|
|
28
|
+
* @param message 错误消息
|
|
29
|
+
* @param error 错误对象
|
|
30
|
+
* @param severity 错误级别
|
|
31
|
+
*/
|
|
32
|
+
static handle(
|
|
33
|
+
context: string,
|
|
34
|
+
message: string,
|
|
35
|
+
error?: unknown,
|
|
36
|
+
severity: ErrorSeverity = ErrorSeverity.ERROR
|
|
37
|
+
): void {
|
|
38
|
+
const errorObj = error instanceof Error ? error : error ? new Error(String(error)) : undefined;
|
|
39
|
+
|
|
40
|
+
switch (severity) {
|
|
41
|
+
case ErrorSeverity.DEBUG:
|
|
42
|
+
this.logger.debug(context, message, errorObj);
|
|
43
|
+
break;
|
|
44
|
+
case ErrorSeverity.INFO:
|
|
45
|
+
this.logger.info(context, message, errorObj);
|
|
46
|
+
break;
|
|
47
|
+
case ErrorSeverity.WARN:
|
|
48
|
+
this.logger.warn(context, message, errorObj);
|
|
49
|
+
break;
|
|
50
|
+
case ErrorSeverity.ERROR:
|
|
51
|
+
default:
|
|
52
|
+
this.logger.error(context, message, errorObj);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 处理异步错误
|
|
58
|
+
* @param context 错误上下文
|
|
59
|
+
* @param message 错误消息
|
|
60
|
+
* @returns 返回一个错误处理函数
|
|
61
|
+
*/
|
|
62
|
+
static asyncHandler(
|
|
63
|
+
context: string,
|
|
64
|
+
message: string
|
|
65
|
+
): (error: unknown) => void {
|
|
66
|
+
return (error: unknown) => {
|
|
67
|
+
this.handle(context, message, error);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 安全执行异步函数并处理错误
|
|
73
|
+
* @param fn 异步函数
|
|
74
|
+
* @param context 错误上下文
|
|
75
|
+
* @param fallbackValue 错误时的返回值
|
|
76
|
+
* @returns 函数结果或 fallbackValue
|
|
77
|
+
*/
|
|
78
|
+
static async safeExecute<T>(
|
|
79
|
+
fn: () => Promise<T>,
|
|
80
|
+
context: string,
|
|
81
|
+
fallbackValue: T
|
|
82
|
+
): Promise<T> {
|
|
83
|
+
try {
|
|
84
|
+
return await fn();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.handle(context, 'Operation failed', error);
|
|
87
|
+
return fallbackValue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 安全执行函数并处理错误
|
|
93
|
+
* @param fn 函数
|
|
94
|
+
* @param context 错误上下文
|
|
95
|
+
* @param fallbackValue 错误时的返回值
|
|
96
|
+
* @returns 函数结果或 fallbackValue
|
|
97
|
+
*/
|
|
98
|
+
static safeExecuteSync<T>(
|
|
99
|
+
fn: () => T,
|
|
100
|
+
context: string,
|
|
101
|
+
fallbackValue: T
|
|
102
|
+
): T {
|
|
103
|
+
try {
|
|
104
|
+
return fn();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.handle(context, 'Operation failed', error);
|
|
107
|
+
return fallbackValue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
browserCapabilities
|
|
17
|
+
} from './index';
|
|
18
|
+
|
|
19
|
+
describe('delay', () => {
|
|
20
|
+
it('should delay for specified milliseconds', async () => {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
await delay(50);
|
|
23
|
+
const elapsed = Date.now() - start;
|
|
24
|
+
|
|
25
|
+
expect(elapsed).toBeGreaterThanOrEqual(45);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('throttle', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
jest.useFakeTimers();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
jest.useRealTimers();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should throttle function calls', () => {
|
|
39
|
+
let count = 0;
|
|
40
|
+
const fn = throttle(() => count++, 100);
|
|
41
|
+
|
|
42
|
+
fn();
|
|
43
|
+
fn();
|
|
44
|
+
fn();
|
|
45
|
+
|
|
46
|
+
expect(count).toBe(1);
|
|
47
|
+
|
|
48
|
+
jest.advanceTimersByTime(100);
|
|
49
|
+
|
|
50
|
+
fn();
|
|
51
|
+
expect(count).toBe(2);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('debounce', () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
jest.useFakeTimers();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
jest.useRealTimers();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should debounce function calls', () => {
|
|
65
|
+
let count = 0;
|
|
66
|
+
const fn = debounce(() => count++, 100);
|
|
67
|
+
|
|
68
|
+
fn();
|
|
69
|
+
fn();
|
|
70
|
+
fn();
|
|
71
|
+
|
|
72
|
+
expect(count).toBe(0);
|
|
73
|
+
|
|
74
|
+
jest.advanceTimersByTime(100);
|
|
75
|
+
|
|
76
|
+
expect(count).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should execute immediately when immediate is true', () => {
|
|
80
|
+
let count = 0;
|
|
81
|
+
const fn = debounce(() => count++, 100, true);
|
|
82
|
+
|
|
83
|
+
fn();
|
|
84
|
+
// Immediate should execute once
|
|
85
|
+
expect(count).toBe(1);
|
|
86
|
+
|
|
87
|
+
// Advance time and check if it runs again
|
|
88
|
+
jest.advanceTimersByTime(100);
|
|
89
|
+
// Depending on implementation, this may vary
|
|
90
|
+
expect(count).toBeGreaterThanOrEqual(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 handle decimals', () => {
|
|
103
|
+
expect(formatBytes(1536, 2)).toBe('1.5 KB');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('generateUUID', () => {
|
|
108
|
+
it('should generate valid UUID', () => {
|
|
109
|
+
const uuid = generateUUID();
|
|
110
|
+
|
|
111
|
+
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should generate unique UUIDs', () => {
|
|
115
|
+
const uuids = new Set<string>();
|
|
116
|
+
for (let i = 0; i < 100; i++) {
|
|
117
|
+
uuids.add(generateUUID());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
expect(uuids.size).toBe(100);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('chunk', () => {
|
|
125
|
+
it('should split array into chunks', () => {
|
|
126
|
+
const arr = [1, 2, 3, 4, 5, 6, 7];
|
|
127
|
+
const chunks = chunk(arr, 3);
|
|
128
|
+
|
|
129
|
+
expect(chunks).toEqual([[1, 2, 3], [4, 5, 6], [7]]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should throw error for invalid chunk size', () => {
|
|
133
|
+
expect(() => chunk([1, 2, 3], 0)).toThrow();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('safeParseJSON', () => {
|
|
138
|
+
it('should parse valid JSON', () => {
|
|
139
|
+
const result = safeParseJSON('{"a": 1}', { default: true });
|
|
140
|
+
expect(result).toEqual({ a: 1 });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return fallback for invalid JSON', () => {
|
|
144
|
+
const result = safeParseJSON<boolean>('invalid', true);
|
|
145
|
+
expect(result).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('clamp', () => {
|
|
150
|
+
it('should clamp value within range', () => {
|
|
151
|
+
expect(clamp(5, 0, 10)).toBe(5);
|
|
152
|
+
expect(clamp(-5, 0, 10)).toBe(0);
|
|
153
|
+
expect(clamp(15, 0, 10)).toBe(10);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('isValidUrl', () => {
|
|
158
|
+
it('should validate URLs', () => {
|
|
159
|
+
expect(isValidUrl('https://example.com')).toBe(true);
|
|
160
|
+
expect(isValidUrl('http://test.com/path')).toBe(true);
|
|
161
|
+
expect(isValidUrl('invalid')).toBe(false);
|
|
162
|
+
expect(isValidUrl('not a url')).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('browserCapabilities', () => {
|
|
167
|
+
it('should check camera support', () => {
|
|
168
|
+
expect(typeof browserCapabilities.hasCamera).toBe('function');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should check WebAssembly support', () => {
|
|
172
|
+
expect(typeof browserCapabilities.hasWasm).toBe('function');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should check WebWorker support', () => {
|
|
176
|
+
expect(typeof browserCapabilities.hasWebWorker).toBe('function');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should check WebGL support', () => {
|
|
180
|
+
expect(typeof browserCapabilities.hasWebGL).toBe('function');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should use supports method', () => {
|
|
184
|
+
expect(typeof browserCapabilities.supports).toBe('function');
|
|
185
|
+
});
|
|
186
|
+
});
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 重试工具测试
|
|
3
|
+
* @description 测试重试功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { withRetry, AsyncCache, Semaphore } from './retry';
|
|
7
|
+
|
|
8
|
+
describe('withRetry', () => {
|
|
9
|
+
it('should retry on failure and succeed', async () => {
|
|
10
|
+
let attempts = 0;
|
|
11
|
+
const fn = async () => {
|
|
12
|
+
attempts++;
|
|
13
|
+
if (attempts < 3) throw new Error('fail');
|
|
14
|
+
return 'success';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const result = await withRetry(fn, {
|
|
18
|
+
maxAttempts: 3,
|
|
19
|
+
initialDelay: 10
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(result).toBe('success');
|
|
23
|
+
expect(attempts).toBe(3);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should fail after max attempts', async () => {
|
|
27
|
+
const fn = async () => {
|
|
28
|
+
throw new Error('always fails');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
await expect(
|
|
32
|
+
withRetry(fn, { maxAttempts: 2, initialDelay: 10 })
|
|
33
|
+
).rejects.toThrow('always fails');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should not retry if shouldRetry returns false', async () => {
|
|
37
|
+
let attempts = 0;
|
|
38
|
+
const fn = async () => {
|
|
39
|
+
attempts++;
|
|
40
|
+
throw new Error('test');
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await expect(
|
|
44
|
+
withRetry(fn, {
|
|
45
|
+
maxAttempts: 3,
|
|
46
|
+
initialDelay: 10,
|
|
47
|
+
shouldRetry: () => false
|
|
48
|
+
})
|
|
49
|
+
).rejects.toThrow();
|
|
50
|
+
|
|
51
|
+
expect(attempts).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('AsyncCache', () => {
|
|
56
|
+
it('should store and retrieve values', async () => {
|
|
57
|
+
const cache = new AsyncCache<string>(1000);
|
|
58
|
+
cache.set('key1', 'value1');
|
|
59
|
+
|
|
60
|
+
expect(cache.get('key1')).toBe('value1');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return undefined for expired cache', async () => {
|
|
64
|
+
const cache = new AsyncCache<string>(50);
|
|
65
|
+
cache.set('key1', 'value1');
|
|
66
|
+
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, 60));
|
|
68
|
+
|
|
69
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should getOrSet value if not exists', async () => {
|
|
73
|
+
const cache = new AsyncCache<string>(1000);
|
|
74
|
+
|
|
75
|
+
const result = await cache.getOrSet('key1', async () => 'computed');
|
|
76
|
+
|
|
77
|
+
expect(result).toBe('computed');
|
|
78
|
+
expect(cache.get('key1')).toBe('computed');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should not recompute if exists', async () => {
|
|
82
|
+
const cache = new AsyncCache<string>(1000);
|
|
83
|
+
let computeCount = 0;
|
|
84
|
+
|
|
85
|
+
const compute = async () => {
|
|
86
|
+
computeCount++;
|
|
87
|
+
return 'value';
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await cache.getOrSet('key1', compute);
|
|
91
|
+
await cache.getOrSet('key1', compute);
|
|
92
|
+
|
|
93
|
+
expect(computeCount).toBe(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should clear all cache', async () => {
|
|
97
|
+
const cache = new AsyncCache<string>(1000);
|
|
98
|
+
cache.set('key1', 'value1');
|
|
99
|
+
cache.set('key2', 'value2');
|
|
100
|
+
|
|
101
|
+
expect(cache.size).toBe(2);
|
|
102
|
+
|
|
103
|
+
cache.clear();
|
|
104
|
+
expect(cache.size).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('Semaphore', () => {
|
|
109
|
+
it('should acquire and release permits', async () => {
|
|
110
|
+
const sem = new Semaphore(2);
|
|
111
|
+
|
|
112
|
+
expect(sem.availablePermits).toBe(2);
|
|
113
|
+
|
|
114
|
+
await sem.acquire();
|
|
115
|
+
expect(sem.availablePermits).toBe(1);
|
|
116
|
+
|
|
117
|
+
await sem.acquire();
|
|
118
|
+
expect(sem.availablePermits).toBe(0);
|
|
119
|
+
|
|
120
|
+
sem.release();
|
|
121
|
+
expect(sem.availablePermits).toBe(1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should tryAcquire returns false when no permits', () => {
|
|
125
|
+
const sem = new Semaphore(1);
|
|
126
|
+
sem.acquire();
|
|
127
|
+
|
|
128
|
+
expect(sem.tryAcquire()).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should queue waiting acquires', async () => {
|
|
132
|
+
const sem = new Semaphore(1);
|
|
133
|
+
|
|
134
|
+
// First acquire
|
|
135
|
+
await sem.acquire();
|
|
136
|
+
expect(sem.availablePermits).toBe(0);
|
|
137
|
+
|
|
138
|
+
// Release
|
|
139
|
+
sem.release();
|
|
140
|
+
expect(sem.availablePermits).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
});
|