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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Image Utilities
3
+ * @description Image processing utility functions
4
+ * @module core/utils/image
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @file Core Utils 入口
3
+ * @description 导出所有核心工具模块
4
+ * @module core/utils
5
+ */
6
+
7
+ export { CanvasPool, PooledCanvas, globalCanvasPool } from './canvas-pool';
8
+ export { WorkerBridge, WorkerMessage, WorkerResponse } from './worker';
9
+ export { ModelCacheManager, ModelShard, ModelCacheInfo, modelCache } from './resource-manager';
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @file 模型分片缓存管理器
3
+ * @description 提供分片模型的 IndexedDB 持久化缓存功能(idb-keyval 风格实现)
4
+ * @module core/utils/resource-manager
5
+ */
6
+
7
+ /**
8
+ * 模型分片信息
9
+ */
10
+ export interface ModelShard {
11
+ name: string; // e.g. 'face-detector'
12
+ version: string; // e.g. '1.0.0'
13
+ url: string; // 完整 URL
14
+ size: number; // bytes
15
+ hash?: string; // 完整性校验
16
+ }
17
+
18
+ /**
19
+ * 模型缓存信息
20
+ */
21
+ export interface ModelCacheInfo {
22
+ name: string;
23
+ version: string;
24
+ cachedAt: number;
25
+ size: number;
26
+ }
27
+
28
+ /**
29
+ * IndexedDB 模型缓存管理器
30
+ * 提供分片模型的持久化缓存功能
31
+ */
32
+ export class ModelCacheManager {
33
+ private _dbName = 'id-scanner-lib-models';
34
+ private _storeName = 'model-cache';
35
+ private _db: IDBDatabase | null = null;
36
+
37
+ /**
38
+ * 初始化缓存管理器(打开 IndexedDB)
39
+ */
40
+ async init(): Promise<void> {
41
+ return new Promise((resolve, reject) => {
42
+ const request = indexedDB.open(this._dbName, 1);
43
+ request.onerror = () => reject(new Error('Failed to open IndexedDB'));
44
+ request.onsuccess = () => {
45
+ this._db = request.result;
46
+ resolve();
47
+ };
48
+ request.onupgradeneeded = (event) => {
49
+ const db = (event.target as IDBOpenDBRequest).result;
50
+ if (!db.objectStoreNames.contains(this._storeName)) {
51
+ const store = db.createObjectStore(this._storeName, { keyPath: 'key' });
52
+ store.createIndex('name', 'name', { unique: false });
53
+ store.createIndex('version', 'version', { unique: false });
54
+ }
55
+ };
56
+ });
57
+ }
58
+
59
+ /**
60
+ * 生成缓存键
61
+ */
62
+ private _makeKey(model: ModelShard): string {
63
+ return `${model.name}@${model.version}`;
64
+ }
65
+
66
+ /**
67
+ * 检查模型是否已缓存
68
+ */
69
+ async has(model: ModelShard): Promise<boolean> {
70
+ if (!this._db) return false;
71
+ return new Promise((resolve, reject) => {
72
+ const tx = this._db!.transaction(this._storeName, 'readonly');
73
+ const store = tx.objectStore(this._storeName);
74
+ const req = store.get(this._makeKey(model));
75
+ req.onsuccess = () => resolve(!!req.result);
76
+ req.onerror = () => reject(req.error);
77
+ });
78
+ }
79
+
80
+ /**
81
+ * 存储模型数据
82
+ */
83
+ async store(model: ModelShard, data: ArrayBuffer): Promise<void> {
84
+ if (!this._db) throw new Error('ModelCacheManager not initialized');
85
+ return new Promise((resolve, reject) => {
86
+ const tx = this._db!.transaction(this._storeName, 'readwrite');
87
+ const store = tx.objectStore(this._storeName);
88
+ const record = {
89
+ key: this._makeKey(model),
90
+ name: model.name,
91
+ version: model.version,
92
+ url: model.url,
93
+ size: model.size,
94
+ data,
95
+ cachedAt: Date.now(),
96
+ };
97
+ const req = store.put(record);
98
+ req.onsuccess = () => resolve();
99
+ req.onerror = () => reject(req.error);
100
+ });
101
+ }
102
+
103
+ /**
104
+ * 加载模型数据
105
+ */
106
+ async load(model: ModelShard): Promise<ArrayBuffer | null> {
107
+ if (!this._db) throw new Error('ModelCacheManager not initialized');
108
+ return new Promise((resolve, reject) => {
109
+ const tx = this._db!.transaction(this._storeName, 'readonly');
110
+ const store = tx.objectStore(this._storeName);
111
+ const req = store.get(this._makeKey(model));
112
+ req.onsuccess = () => resolve(req.result ? req.result.data : null);
113
+ req.onerror = () => reject(req.error);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * 清除所有缓存
119
+ */
120
+ async clear(): Promise<void> {
121
+ if (!this._db) return;
122
+ return new Promise((resolve, reject) => {
123
+ const tx = this._db!.transaction(this._storeName, 'readwrite');
124
+ const store = tx.objectStore(this._storeName);
125
+ const req = store.clear();
126
+ req.onsuccess = () => resolve();
127
+ req.onerror = () => reject(req.error);
128
+ });
129
+ }
130
+
131
+ /**
132
+ * 列出已缓存模型
133
+ */
134
+ async listCached(): Promise<ModelCacheInfo[]> {
135
+ if (!this._db) return [];
136
+ return new Promise((resolve, reject) => {
137
+ const tx = this._db!.transaction(this._storeName, 'readonly');
138
+ const store = tx.objectStore(this._storeName);
139
+ const req = store.getAll();
140
+ req.onsuccess = () => {
141
+ const results: ModelCacheInfo[] = req.result.map((r: any) => ({
142
+ name: r.name,
143
+ version: r.version,
144
+ cachedAt: r.cachedAt,
145
+ size: r.size,
146
+ }));
147
+ resolve(results);
148
+ };
149
+ req.onerror = () => reject(req.error);
150
+ });
151
+ }
152
+ }
153
+
154
+ /** 全局模型缓存管理器实例 */
155
+ export const modelCache = new ModelCacheManager();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Validation Utilities
3
+ * @description Input validation utility functions
4
+ * @module core/utils/validate
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @file WorkerBridge - Web Worker 并行处理框架
3
+ * @description 提供基于 Promise 的 Worker 通信接口,支持超时和错误处理
4
+ * @module core/utils/worker
5
+ */
6
+
7
+ /**
8
+ * Worker 消息格式
9
+ */
10
+ export type WorkerMessage<T = any> = {
11
+ type: string;
12
+ payload: T;
13
+ id: string;
14
+ };
15
+
16
+ /**
17
+ * Worker 响应格式
18
+ */
19
+ export type WorkerResponse<T = any> = {
20
+ type: string;
21
+ payload: T;
22
+ error?: string;
23
+ id: string;
24
+ };
25
+
26
+ /**
27
+ * WorkerBridge - 基于 Promise 的 Worker 通信桥接器
28
+ *
29
+ * 提供简洁的 Worker 消息发送接口,自动处理请求-响应匹配、超时和错误
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const bridge = new WorkerBridge('/workers/face-detect.worker.ts');
34
+ * await bridge.load();
35
+ * const result = await bridge.post('detect', { imageData });
36
+ * bridge.terminate();
37
+ * ```
38
+ */
39
+ export class WorkerBridge<TReq = any, TRes = any> {
40
+ private _worker: Worker | null = null;
41
+ private _pending: Map<string, { resolve: Function; reject: Function }> = new Map();
42
+ private _url: string;
43
+
44
+ /**
45
+ * 创建 WorkerBridge
46
+ * @param workerUrl Worker 文件 URL
47
+ */
48
+ constructor(workerUrl: string) {
49
+ this._url = workerUrl;
50
+ }
51
+
52
+ /**
53
+ * 加载 Worker
54
+ */
55
+ async load(): Promise<void> {
56
+ this._worker = new Worker(this._url);
57
+ this._worker.onmessage = (e: MessageEvent<WorkerResponse<TRes>>) => {
58
+ const { id, payload, error } = e.data;
59
+ const handlers = this._pending.get(id);
60
+ if (handlers) {
61
+ if (error) {
62
+ handlers.reject(new Error(error));
63
+ } else {
64
+ handlers.resolve(payload);
65
+ }
66
+ this._pending.delete(id);
67
+ }
68
+ };
69
+ this._worker.onerror = (e) => {
70
+ // 将 Worker 错误广播给所有 pending 的 Promise
71
+ for (const [id, handlers] of this._pending) {
72
+ handlers.reject(new Error(`Worker error: ${e.message}`));
73
+ this._pending.delete(id);
74
+ }
75
+ };
76
+ }
77
+
78
+ /**
79
+ * 发送消息到 Worker 并等待响应
80
+ *
81
+ * @param type 消息类型
82
+ * @param payload 消息载荷
83
+ * @returns 响应 Promise
84
+ * @throws 如果 Worker 未加载则抛出错误
85
+ */
86
+ async post(type: string, payload: TReq): Promise<TRes> {
87
+ if (!this._worker) {
88
+ throw new Error('Worker not loaded');
89
+ }
90
+ const id = crypto.randomUUID();
91
+ return new Promise((resolve, reject) => {
92
+ this._pending.set(id, { resolve, reject });
93
+ this._worker!.postMessage({ type, payload, id } as WorkerMessage<TReq>);
94
+ // 超时 30s
95
+ setTimeout(() => {
96
+ if (this._pending.has(id)) {
97
+ this._pending.delete(id);
98
+ reject(new Error('Worker timeout'));
99
+ }
100
+ }, 30000);
101
+ });
102
+ }
103
+
104
+ /**
105
+ * 终止 Worker
106
+ */
107
+ terminate(): void {
108
+ this._worker?.terminate();
109
+ this._worker = null;
110
+ // 拒绝所有 pending 的 Promise
111
+ for (const [, handlers] of this._pending) {
112
+ handlers.reject(new Error('Worker terminated'));
113
+ }
114
+ this._pending.clear();
115
+ }
116
+
117
+ /**
118
+ * 检查 Worker 是否已加载
119
+ */
120
+ get isLoaded(): boolean {
121
+ return this._worker !== null;
122
+ }
123
+
124
+ /**
125
+ * 获取当前 pending 请求数
126
+ */
127
+ get pendingCount(): number {
128
+ return this._pending.size;
129
+ }
130
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @file Face Comparator
3
+ * @description Face comparison using cosine similarity
4
+ * @module modules/face/comparator
5
+ */
6
+
7
+ import { FaceCompareResult, FaceDetectionResult } from '../detector/types';
8
+
9
+ export class FaceComparator {
10
+ private _threshold: number = 0.6;
11
+
12
+ constructor(threshold: number = 0.6) {
13
+ this._threshold = threshold;
14
+ }
15
+
16
+ async compare(
17
+ face1: FaceDetectionResult | number[],
18
+ face2: FaceDetectionResult | number[]
19
+ ): Promise<FaceCompareResult> {
20
+ // Extract embedding
21
+ const emb1 = Array.isArray(face1) ? face1 : face1.embedding ?? [];
22
+ const emb2 = Array.isArray(face2) ? face2 : face2.embedding ?? [];
23
+
24
+ // Compute cosine similarity
25
+ const similarity = this._cosineSimilarity(emb1, emb2);
26
+
27
+ return {
28
+ similarity,
29
+ isMatch: similarity >= this._threshold,
30
+ threshold: this._threshold,
31
+ algorithm: 'cosine-similarity',
32
+ };
33
+ }
34
+
35
+ private _cosineSimilarity(a: number[], b: number[]): number {
36
+ if (a.length !== b.length || a.length === 0) return 0;
37
+ let dot = 0, magA = 0, magB = 0;
38
+ for (let i = 0; i < a.length; i++) {
39
+ dot += a[i] * b[i];
40
+ magA += a[i] * a[i];
41
+ magB += b[i] * b[i];
42
+ }
43
+ return dot / (Math.sqrt(magA) * Math.sqrt(magB));
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export { FaceComparator } from './comparator';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @file Face Detector
3
+ * @description Face detection module with canvas pooling
4
+ * @module modules/face/detector
5
+ */
6
+
7
+ import { FaceDetectionOptions, FaceDetectionResult } from './types';
8
+ import { CanvasPool } from '../../../core/utils/canvas-pool';
9
+ import { ImageSource } from '../../../core/config';
10
+
11
+ export class FaceDetector {
12
+ private _options: Required<FaceDetectionOptions>;
13
+ private _pool: CanvasPool;
14
+ private _initialized: boolean = false;
15
+ private _modelLoaded: boolean = false;
16
+
17
+ constructor(options: FaceDetectionOptions = {}) {
18
+ this._options = {
19
+ maxFaces: options.maxFaces ?? 10,
20
+ minFaceSize: options.minFaceSize ?? 0.1,
21
+ confidenceThreshold: options.confidenceThreshold ?? 0.5,
22
+ landmarks: options.landmarks ?? true,
23
+ inputWidth: options.inputWidth ?? 640,
24
+ inputHeight: options.inputHeight ?? 480,
25
+ };
26
+ this._pool = new CanvasPool();
27
+ }
28
+
29
+ get initialized(): boolean {
30
+ return this._initialized;
31
+ }
32
+
33
+ async initialize(): Promise<void> {
34
+ // Load model (lazy loading on demand)
35
+ this._modelLoaded = true;
36
+ this._initialized = true;
37
+ }
38
+
39
+ async detect(input: ImageSource): Promise<FaceDetectionResult[]> {
40
+ if (!this._initialized) {
41
+ throw new Error('FaceDetector not initialized');
42
+ }
43
+
44
+ // 1. Acquire canvas from pool
45
+ const pooled = this._pool.acquire(this._options.inputWidth, this._options.inputHeight);
46
+
47
+ try {
48
+ // 2. Draw input to canvas
49
+ const ctx = pooled.canvas.getContext('2d');
50
+ if (!ctx) {
51
+ throw new Error('Failed to get 2d context');
52
+ }
53
+
54
+ // Handle different input types
55
+ if (input instanceof HTMLCanvasElement) {
56
+ ctx.drawImage(input, 0, 0);
57
+ } else if (input instanceof HTMLImageElement || input instanceof HTMLVideoElement) {
58
+ ctx.drawImage(input, 0, 0, this._options.inputWidth, this._options.inputHeight);
59
+ } else if (input instanceof ImageData) {
60
+ ctx.putImageData(input, 0, 0);
61
+ }
62
+ // string input (URL) would need async loading — not implemented in stub
63
+
64
+ // 3. Run detection (stub returns empty)
65
+ // TODO: Integrate real model
66
+
67
+ // 4. Convert results and return
68
+ const results: FaceDetectionResult[] = [];
69
+ // stub: no faces detected
70
+
71
+ return results;
72
+ } finally {
73
+ // 5. Release canvas back to pool
74
+ this._pool.release(pooled);
75
+ }
76
+ }
77
+
78
+ async destroy(): Promise<void> {
79
+ this._pool.clear();
80
+ this._initialized = false;
81
+ this._modelLoaded = false;
82
+ }
83
+ }
@@ -0,0 +1,2 @@
1
+ export { FaceDetector } from './detector';
2
+ export { FaceDetectionOptions, FaceDetectionResult, FaceCompareResult, BoundingBox, FaceLandmarks, Point } from './types';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Face detection options
3
+ */
4
+ export interface FaceDetectionOptions {
5
+ /** Maximum number of faces to detect */
6
+ maxFaces?: number;
7
+ /** Minimum face size (0-1, relative to image) */
8
+ minFaceSize?: number;
9
+ /** Confidence threshold (0-1) */
10
+ confidenceThreshold?: number;
11
+ /** Whether to return facial landmarks */
12
+ landmarks?: boolean;
13
+ /** Input image/frame width (for normalization) */
14
+ inputWidth?: number;
15
+ /** Input image/frame height (for normalization) */
16
+ inputHeight?: number;
17
+ }
18
+
19
+ /**
20
+ * Detected face result
21
+ */
22
+ export interface FaceDetectionResult {
23
+ /** Unique face ID (for tracking) */
24
+ faceId: string;
25
+ /** Bounding box */
26
+ box: BoundingBox;
27
+ /** Detection confidence (0-1) */
28
+ confidence: number;
29
+ /** Facial landmarks (optional) */
30
+ landmarks?: FaceLandmarks;
31
+ /** Face embedding vector (optional, for comparison) */
32
+ embedding?: number[];
33
+ /** Detection timestamp */
34
+ timestamp: number;
35
+ }
36
+
37
+ /**
38
+ * Bounding box
39
+ */
40
+ export interface BoundingBox {
41
+ x: number;
42
+ y: number;
43
+ width: number;
44
+ height: number;
45
+ }
46
+
47
+ /**
48
+ * Face landmarks
49
+ */
50
+ export interface FaceLandmarks {
51
+ leftEye?: Point;
52
+ rightEye?: Point;
53
+ nose?: Point;
54
+ leftMouth?: Point;
55
+ rightMouth?: Point;
56
+ leftEar?: Point;
57
+ rightEar?: Point;
58
+ }
59
+
60
+ /**
61
+ * 2D point
62
+ */
63
+ export interface Point {
64
+ x: number;
65
+ y: number;
66
+ }
67
+
68
+ /**
69
+ * Face comparison result
70
+ */
71
+ export interface FaceCompareResult {
72
+ /** Similarity score (0-1) */
73
+ similarity: number;
74
+ /** Whether faces match (threshold-based) */
75
+ isMatch: boolean;
76
+ /** Threshold used */
77
+ threshold: number;
78
+ /** Comparison algorithm used */
79
+ algorithm: string;
80
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Face Liveness Module
3
+ * @description Face liveness detection module
4
+ * @module modules/face/liveness
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Face Tracker Module
3
+ * @description Face tracking module
4
+ * @module modules/face/tracker
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file ID Card Anti-Fake Module
3
+ * @description ID card anti-counterfeiting detection module
4
+ * @module modules/id-card/anti-fake
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file ID Card Detector Module
3
+ * @description ID card detection module
4
+ * @module modules/id-card/detector
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file ID Card Parser Module
3
+ * @description ID card text parsing module
4
+ * @module modules/id-card/parser
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file QR Scanner Module
3
+ * @description QR code scanning module
4
+ * @module modules/qr/scanner
5
+ */
6
+
7
+ export {};