taro-bluetooth-print 2.3.1 → 2.4.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 (35) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +6 -1
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.es.js +1 -1
  5. package/dist/index.umd.js +1 -1
  6. package/dist/types/config/PrinterConfigManager.d.ts +206 -0
  7. package/dist/types/config/index.d.ts +8 -0
  8. package/dist/types/device/MultiPrinterManager.d.ts +164 -0
  9. package/dist/types/device/index.d.ts +2 -0
  10. package/dist/types/index.d.ts +5 -1
  11. package/dist/types/services/BatchPrintManager.d.ts +205 -0
  12. package/dist/types/services/PrintHistory.d.ts +142 -0
  13. package/dist/types/services/PrintJobManager.d.ts +28 -4
  14. package/dist/types/services/PrinterStatus.d.ts +97 -0
  15. package/dist/types/services/index.d.ts +3 -0
  16. package/package.json +2 -2
  17. package/src/adapters/AlipayAdapter.ts +1 -0
  18. package/src/adapters/BaiduAdapter.ts +1 -0
  19. package/src/adapters/ByteDanceAdapter.ts +1 -0
  20. package/src/adapters/TaroAdapter.ts +1 -0
  21. package/src/adapters/WebBluetoothAdapter.ts +1 -1
  22. package/src/config/PrinterConfigManager.ts +519 -0
  23. package/src/config/index.ts +15 -0
  24. package/src/device/MultiPrinterManager.ts +470 -0
  25. package/src/device/index.ts +8 -0
  26. package/src/encoding/gbk-lite.ts +81 -76
  27. package/src/encoding/gbk-table.ts +14 -14
  28. package/src/index.ts +16 -1
  29. package/src/services/BatchPrintManager.ts +500 -0
  30. package/src/services/ConnectionManager.ts +4 -1
  31. package/src/services/PrintHistory.ts +336 -0
  32. package/src/services/PrintJobManager.ts +69 -9
  33. package/src/services/PrinterStatus.ts +267 -0
  34. package/src/services/index.ts +6 -0
  35. package/src/template/TemplateEngine.ts +4 -1
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Print History Service
3
+ *
4
+ * Tracks all print jobs with timestamps, status, and metadata.
5
+ * Provides querying and analytics capabilities.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const history = new PrintHistory();
10
+ * history.addJob({ data, status: 'completed' });
11
+ * const recentJobs = history.getRecent(10);
12
+ * ```
13
+ */
14
+
15
+ import { Logger } from '@/utils/logger';
16
+ import { PrintJobStatus, PrintJobPriority } from '@/queue/PrintQueue';
17
+
18
+ /**
19
+ * Print history entry
20
+ */
21
+ export interface PrintHistoryEntry {
22
+ /** Unique entry ID */
23
+ id: string;
24
+ /** Job ID from PrintJobManager */
25
+ jobId?: string;
26
+ /** Print data size in bytes */
27
+ dataSize: number;
28
+ /** Job status */
29
+ status: PrintJobStatus | 'unknown';
30
+ /** Priority */
31
+ priority: PrintJobPriority;
32
+ /** Creation timestamp */
33
+ createdAt: number;
34
+ /** Start timestamp */
35
+ startedAt?: number;
36
+ /** Completion timestamp */
37
+ completedAt?: number;
38
+ /** Duration in milliseconds */
39
+ duration?: number;
40
+ /** Error message if failed */
41
+ error?: string;
42
+ /** Device ID */
43
+ deviceId?: string;
44
+ /** Device name */
45
+ deviceName?: string;
46
+ /** Metadata */
47
+ metadata?: Record<string, unknown>;
48
+ }
49
+
50
+ /**
51
+ * History statistics
52
+ */
53
+ export interface PrintHistoryStats {
54
+ /** Total jobs */
55
+ total: number;
56
+ /** Completed jobs */
57
+ completed: number;
58
+ /** Failed jobs */
59
+ failed: number;
60
+ /** Cancelled jobs */
61
+ cancelled: number;
62
+ /** Average duration in ms */
63
+ avgDuration: number;
64
+ /** Total bytes printed */
65
+ totalBytes: number;
66
+ /** Success rate */
67
+ successRate: number;
68
+ }
69
+
70
+ /**
71
+ * Query options for history
72
+ */
73
+ export interface HistoryQueryOptions {
74
+ /** Start date filter */
75
+ startDate?: number;
76
+ /** End date filter */
77
+ endDate?: number;
78
+ /** Status filter */
79
+ status?: PrintJobStatus | PrintJobStatus[];
80
+ /** Device ID filter */
81
+ deviceId?: string;
82
+ /** Limit results */
83
+ limit?: number;
84
+ /** Offset for pagination */
85
+ offset?: number;
86
+ }
87
+
88
+ /**
89
+ * Print History Service
90
+ */
91
+ export class PrintHistory {
92
+ private readonly logger = Logger.scope('PrintHistory');
93
+ private readonly entries: Map<string, PrintHistoryEntry> = new Map();
94
+ private counter = 0;
95
+ private readonly maxEntries: number;
96
+
97
+ /**
98
+ * Creates a new PrintHistory instance
99
+ * @param maxEntries - Maximum number of entries to keep (default: 1000)
100
+ */
101
+ constructor(maxEntries = 1000) {
102
+ this.maxEntries = maxEntries;
103
+ }
104
+
105
+ /**
106
+ * Add a new print job to history
107
+ */
108
+ addJob(params: {
109
+ jobId?: string;
110
+ data: Uint8Array;
111
+ status: PrintJobStatus | 'unknown';
112
+ priority?: PrintJobPriority;
113
+ deviceId?: string;
114
+ deviceName?: string;
115
+ metadata?: Record<string, unknown>;
116
+ }): string {
117
+ const id = this.generateId();
118
+ const now = Date.now();
119
+
120
+ const entry: PrintHistoryEntry = {
121
+ id,
122
+ jobId: params.jobId,
123
+ dataSize: params.data.length,
124
+ status: params.status,
125
+ priority: params.priority ?? PrintJobPriority.NORMAL,
126
+ createdAt: now,
127
+ deviceId: params.deviceId,
128
+ deviceName: params.deviceName,
129
+ metadata: params.metadata,
130
+ };
131
+
132
+ this.entries.set(id, entry);
133
+ this.enforceMaxEntries();
134
+
135
+ this.logger.debug(`History entry added: ${id}`);
136
+ return id;
137
+ }
138
+
139
+ /**
140
+ * Update job status
141
+ */
142
+ updateJob(id: string, updates: Partial<{
143
+ status: PrintJobStatus | 'unknown';
144
+ startedAt: number;
145
+ completedAt: number;
146
+ error: string;
147
+ }>): void {
148
+ const entry = this.entries.get(id);
149
+ if (!entry) {
150
+ this.logger.warn(`History entry not found: ${id}`);
151
+ return;
152
+ }
153
+
154
+ Object.assign(entry, updates);
155
+
156
+ if (updates.startedAt) {
157
+ entry.startedAt = updates.startedAt;
158
+ }
159
+
160
+ if (updates.completedAt) {
161
+ entry.completedAt = updates.completedAt;
162
+ if (entry.startedAt) {
163
+ entry.duration = updates.completedAt - entry.startedAt;
164
+ }
165
+ }
166
+
167
+ if (updates.error) {
168
+ entry.error = updates.error;
169
+ }
170
+
171
+ if (updates.status) {
172
+ entry.status = updates.status;
173
+ }
174
+
175
+ this.logger.debug(`History entry updated: ${id}`);
176
+ }
177
+
178
+ /**
179
+ * Get entry by ID
180
+ */
181
+ getEntry(id: string): PrintHistoryEntry | undefined {
182
+ return this.entries.get(id);
183
+ }
184
+
185
+ /**
186
+ * Get recent jobs
187
+ */
188
+ getRecent(count = 10): PrintHistoryEntry[] {
189
+ return Array.from(this.entries.values())
190
+ .sort((a, b) => b.createdAt - a.createdAt)
191
+ .slice(0, count);
192
+ }
193
+
194
+ /**
195
+ * Query history with filters
196
+ */
197
+ query(options: HistoryQueryOptions = {}): PrintHistoryEntry[] {
198
+ let results = Array.from(this.entries.values());
199
+
200
+ if (options.startDate) {
201
+ results = results.filter(e => e.createdAt >= options.startDate!);
202
+ }
203
+
204
+ if (options.endDate) {
205
+ results = results.filter(e => e.createdAt <= options.endDate!);
206
+ }
207
+
208
+ if (options.status) {
209
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
210
+ results = results.filter(e => statuses.includes(e.status as PrintJobStatus));
211
+ }
212
+
213
+ if (options.deviceId) {
214
+ results = results.filter(e => e.deviceId === options.deviceId);
215
+ }
216
+
217
+ // Sort by creation date descending
218
+ results.sort((a, b) => b.createdAt - a.createdAt);
219
+
220
+ // Pagination
221
+ if (options.offset) {
222
+ results = results.slice(options.offset);
223
+ }
224
+
225
+ if (options.limit) {
226
+ results = results.slice(0, options.limit);
227
+ }
228
+
229
+ return results;
230
+ }
231
+
232
+ /**
233
+ * Get statistics
234
+ */
235
+ getStats(options?: { days?: number }): PrintHistoryStats {
236
+ let entries = Array.from(this.entries.values());
237
+
238
+ // Filter by days if specified
239
+ if (options?.days) {
240
+ const cutoff = Date.now() - options.days * 24 * 60 * 60 * 1000;
241
+ entries = entries.filter(e => e.createdAt >= cutoff);
242
+ }
243
+
244
+ const completed = entries.filter(e => e.status === PrintJobStatus.COMPLETED);
245
+ const failed = entries.filter(e =>
246
+ e.status === PrintJobStatus.FAILED || e.error
247
+ );
248
+ const cancelled = entries.filter(e => e.status === PrintJobStatus.CANCELLED);
249
+
250
+ const totalDuration = completed
251
+ .filter(e => e.duration !== undefined)
252
+ .reduce((sum, e) => sum + (e.duration || 0), 0);
253
+
254
+ const totalBytes = entries.reduce((sum, e) => sum + e.dataSize, 0);
255
+
256
+ return {
257
+ total: entries.length,
258
+ completed: completed.length,
259
+ failed: failed.length,
260
+ cancelled: cancelled.length,
261
+ avgDuration: completed.length > 0 ? totalDuration / completed.length : 0,
262
+ totalBytes,
263
+ successRate: entries.length > 0 ? (completed.length / entries.length) * 100 : 0,
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Clear all history
269
+ */
270
+ clear(): void {
271
+ this.entries.clear();
272
+ this.logger.info('Print history cleared');
273
+ }
274
+
275
+ /**
276
+ * Export history as JSON
277
+ */
278
+ export(): string {
279
+ return JSON.stringify(Array.from(this.entries.values()), null, 2);
280
+ }
281
+
282
+ /**
283
+ * Import history from JSON
284
+ */
285
+ import(json: string): number {
286
+ try {
287
+ const data = JSON.parse(json) as PrintHistoryEntry[];
288
+ let imported = 0;
289
+
290
+ for (const entry of data) {
291
+ if (entry.id && entry.dataSize) {
292
+ this.entries.set(entry.id, entry);
293
+ imported++;
294
+ }
295
+ }
296
+
297
+ this.enforceMaxEntries();
298
+ this.logger.info(`Imported ${imported} history entries`);
299
+ return imported;
300
+ } catch (error) {
301
+ this.logger.error('Failed to import history:', error);
302
+ return 0;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Generate unique ID
308
+ */
309
+ private generateId(): string {
310
+ this.counter++;
311
+ return `history_${Date.now()}_${this.counter}`;
312
+ }
313
+
314
+ /**
315
+ * Enforce maximum entries limit
316
+ */
317
+ private enforceMaxEntries(): void {
318
+ if (this.entries.size <= this.maxEntries) {
319
+ return;
320
+ }
321
+
322
+ // Remove oldest entries
323
+ const sorted = Array.from(this.entries.entries())
324
+ .sort((a, b) => a[1].createdAt - b[1].createdAt);
325
+
326
+ const toRemove = sorted.slice(0, this.entries.size - this.maxEntries);
327
+ for (const [id] of toRemove) {
328
+ this.entries.delete(id);
329
+ }
330
+
331
+ this.logger.debug(`Pruned ${toRemove.length} old history entries`);
332
+ }
333
+ }
334
+
335
+ // Export singleton instance
336
+ export const printHistory = new PrintHistory();
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Print Job Manager Service
3
3
  *
4
- * Manages print jobs, including pause/resume/cancel functionality
4
+ * Manages print jobs, including pause/resume/cancel functionality.
5
+ * Supports job state persistence for resume capability.
5
6
  */
6
7
 
7
8
  import type { IPrinterAdapter } from '@/types';
@@ -37,8 +38,19 @@ interface SavedJobState {
37
38
  * Print Job Manager implementation
38
39
  */
39
40
  export class PrintJobManager implements IPrintJobManager {
40
- /** 内存中的任务状态存储(可被子类或外部替换为持久化方案) */
41
- private static jobStateStore: Map<string, SavedJobState> = new Map();
41
+ /** Instance-level job state storage (per-printer support) */
42
+ private instanceJobStateStore: Map<string, SavedJobState> = new Map();
43
+
44
+ /** Static job state store for backward compatibility */
45
+ private static _jobStateStore: Map<string, SavedJobState> = new Map();
46
+
47
+ /**
48
+ * Get the static job state store (for backward compatibility)
49
+ * @deprecated Use instance-level store instead for multi-printer support
50
+ */
51
+ private static get jobStateStore(): Map<string, SavedJobState> {
52
+ return PrintJobManager._jobStateStore;
53
+ }
42
54
  private adapter: IPrinterAdapter;
43
55
  private connectionManager: IConnectionManager;
44
56
  private jobBuffer: Uint8Array | null = null;
@@ -221,8 +233,8 @@ export class PrintJobManager implements IPrintJobManager {
221
233
  /**
222
234
  * Saves the current job state for resume later.
223
235
  *
224
- * 默认实现使用内存存储。如需持久化(如 localStorage),
225
- * 可通过 setSaveHandler/setLoadHandler 自定义。
236
+ * Uses instance-level storage by default. Falls back to static store
237
+ * for backward compatibility.
226
238
  */
227
239
  private saveJobState(): void {
228
240
  if (!this.jobBuffer || !this.jobId) {
@@ -230,14 +242,16 @@ export class PrintJobManager implements IPrintJobManager {
230
242
  }
231
243
 
232
244
  try {
233
- const state = {
245
+ const state: SavedJobState = {
234
246
  jobId: this.jobId,
235
247
  jobBuffer: Array.from(this.jobBuffer),
236
248
  jobOffset: this.jobOffset,
237
- adapterOptions: this.adapterOptions,
249
+ adapterOptions: { ...this.adapterOptions },
238
250
  timestamp: Date.now(),
239
251
  };
240
252
 
253
+ // Save to both instance and static store for backward compatibility
254
+ this.instanceJobStateStore.set(this.jobId, state);
241
255
  PrintJobManager.jobStateStore.set(this.jobId, state);
242
256
 
243
257
  this.logger.debug(
@@ -257,13 +271,17 @@ export class PrintJobManager implements IPrintJobManager {
257
271
  try {
258
272
  this.logger.debug(`Loading job state for ${jobId}`);
259
273
 
260
- const savedState = PrintJobManager.jobStateStore.get(jobId);
274
+ // Try instance store first, then fall back to static store
275
+ let savedState = this.instanceJobStateStore.get(jobId);
276
+ if (!savedState) {
277
+ savedState = PrintJobManager.jobStateStore.get(jobId);
278
+ }
261
279
 
262
280
  if (savedState) {
263
281
  this.jobId = savedState.jobId;
264
282
  this.jobBuffer = new Uint8Array(savedState.jobBuffer);
265
283
  this.jobOffset = savedState.jobOffset;
266
- this.adapterOptions = savedState.adapterOptions;
284
+ this.adapterOptions = { ...savedState.adapterOptions };
267
285
  this._isPaused = true;
268
286
  this._isInProgress = true;
269
287
  this.logger.info(
@@ -288,6 +306,8 @@ export class PrintJobManager implements IPrintJobManager {
288
306
  private clearJobState(): void {
289
307
  if (this.jobId) {
290
308
  this.logger.debug(`Clearing job state for ${this.jobId}`);
309
+ // Clear from both stores
310
+ this.instanceJobStateStore.delete(this.jobId);
291
311
  PrintJobManager.jobStateStore.delete(this.jobId);
292
312
  }
293
313
 
@@ -297,6 +317,46 @@ export class PrintJobManager implements IPrintJobManager {
297
317
  this.adapterOptions = {};
298
318
  }
299
319
 
320
+ /**
321
+ * Cleanup resources and clear all job state.
322
+ * Call this when the printer is no longer needed.
323
+ */
324
+ destroy(): void {
325
+ this.cancel();
326
+ this.instanceJobStateStore.clear();
327
+ this.onProgress = undefined;
328
+ this.onJobStateChange = undefined;
329
+ this.logger.info('PrintJobManager destroyed');
330
+ }
331
+
332
+ /**
333
+ * Clean up expired job states from static store.
334
+ * Call this periodically to prevent memory leaks.
335
+ *
336
+ * @param maxAge - Maximum age in ms (default: 1 hour)
337
+ */
338
+ static cleanupExpiredJobs(maxAge = 3600000): number {
339
+ const now = Date.now();
340
+ let cleaned = 0;
341
+
342
+ for (const [jobId, state] of PrintJobManager.jobStateStore.entries()) {
343
+ if (now - state.timestamp > maxAge) {
344
+ PrintJobManager.jobStateStore.delete(jobId);
345
+ cleaned++;
346
+ }
347
+ }
348
+
349
+ return cleaned;
350
+ }
351
+
352
+ /**
353
+ * Get count of pending job states in static store.
354
+ * Useful for debugging memory usage.
355
+ */
356
+ static getStaticStoreSize(): number {
357
+ return PrintJobManager.jobStateStore.size;
358
+ }
359
+
300
360
  /**
301
361
  * Emits job state change event
302
362
  *