taro-bluetooth-print 2.3.0 → 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.
- package/CHANGELOG.md +73 -195
- package/README.md +134 -386
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/config/PrinterConfigManager.d.ts +206 -0
- package/dist/types/config/index.d.ts +8 -0
- package/dist/types/core/BluetoothPrinter.d.ts +1 -1
- package/dist/types/core/EventEmitter.d.ts +6 -26
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/device/MultiPrinterManager.d.ts +164 -0
- package/dist/types/device/index.d.ts +2 -0
- package/dist/types/drivers/CpclDriver.d.ts +304 -0
- package/dist/types/drivers/GPrinterDriver.d.ts +63 -0
- package/dist/types/drivers/ZplDriver.d.ts +325 -0
- package/dist/types/drivers/index.d.ts +9 -0
- package/dist/types/encoding/gbk-lite.d.ts +8 -0
- package/dist/types/encoding/gbk-table.d.ts +8 -30
- package/dist/types/index.d.ts +12 -8
- package/dist/types/services/BatchPrintManager.d.ts +205 -0
- package/dist/types/services/ConnectionManager.d.ts +1 -1
- package/dist/types/services/PrintHistory.d.ts +142 -0
- package/dist/types/services/PrintJobManager.d.ts +28 -4
- package/dist/types/services/PrinterStatus.d.ts +97 -0
- package/dist/types/services/index.d.ts +11 -0
- package/package.json +25 -7
- package/src/adapters/AlipayAdapter.ts +1 -0
- package/src/adapters/BaiduAdapter.ts +1 -0
- package/src/adapters/BaseAdapter.ts +6 -8
- package/src/adapters/ByteDanceAdapter.ts +1 -0
- package/src/adapters/TaroAdapter.ts +1 -0
- package/src/adapters/WebBluetoothAdapter.ts +1 -1
- package/src/config/PrinterConfigManager.ts +519 -0
- package/src/config/index.ts +15 -0
- package/src/core/BluetoothPrinter.ts +15 -15
- package/src/core/EventEmitter.ts +15 -15
- package/src/core/index.ts +7 -0
- package/src/device/MultiPrinterManager.ts +470 -0
- package/src/device/index.ts +8 -0
- package/src/drivers/CpclDriver.ts +549 -0
- package/src/drivers/GPrinterDriver.ts +115 -0
- package/src/drivers/TsplDriver.ts +9 -21
- package/src/drivers/ZplDriver.ts +543 -0
- package/src/drivers/index.ts +37 -0
- package/src/encoding/gbk-lite.ts +113 -0
- package/src/encoding/gbk-table.ts +80 -58
- package/src/index.ts +40 -35
- package/src/plugins/PluginManager.ts +3 -1
- package/src/plugins/builtin/LoggingPlugin.ts +4 -2
- package/src/plugins/builtin/RetryPlugin.ts +8 -14
- package/src/services/BatchPrintManager.ts +500 -0
- package/src/services/ConnectionManager.ts +25 -22
- package/src/services/PrintHistory.ts +336 -0
- package/src/services/PrintJobManager.ts +69 -9
- package/src/services/PrinterStatus.ts +267 -0
- package/src/services/index.ts +22 -0
- package/src/template/TemplateEngine.ts +4 -1
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Print Manager
|
|
3
|
+
*
|
|
4
|
+
* Optimizes printing multiple jobs by:
|
|
5
|
+
* - Merging small jobs into larger chunks
|
|
6
|
+
* - Reducing Bluetooth communication overhead
|
|
7
|
+
* - Prioritizing urgent jobs
|
|
8
|
+
* - Batching similar content for efficiency
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const batchManager = new BatchPrintManager();
|
|
13
|
+
*
|
|
14
|
+
* // Add print jobs
|
|
15
|
+
* batchManager.addJob({ data: buffer1, priority: 1 });
|
|
16
|
+
* batchManager.addJob({ data: buffer2, priority: 2 });
|
|
17
|
+
*
|
|
18
|
+
* // Process batch when ready
|
|
19
|
+
* await batchManager.processBatch();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Logger } from '@/utils/logger';
|
|
24
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Batch job entry
|
|
28
|
+
*/
|
|
29
|
+
export interface BatchJob {
|
|
30
|
+
/** Unique job ID */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Print data */
|
|
33
|
+
data: Uint8Array;
|
|
34
|
+
/** Priority (higher = more urgent) */
|
|
35
|
+
priority: number;
|
|
36
|
+
/** Timestamp when added */
|
|
37
|
+
addedAt: number;
|
|
38
|
+
/** Metadata */
|
|
39
|
+
metadata?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Batch configuration
|
|
44
|
+
*/
|
|
45
|
+
export interface BatchConfig {
|
|
46
|
+
/** Maximum batch size in bytes */
|
|
47
|
+
maxBatchSize: number;
|
|
48
|
+
/** Maximum wait time before processing in ms */
|
|
49
|
+
maxWaitTime: number;
|
|
50
|
+
/** Minimum jobs before batching */
|
|
51
|
+
minBatchSize: number;
|
|
52
|
+
/** Merge similar content */
|
|
53
|
+
enableMerging: boolean;
|
|
54
|
+
/** Auto-process interval in ms (0 = disabled) */
|
|
55
|
+
autoProcessInterval: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Batch statistics
|
|
60
|
+
*/
|
|
61
|
+
export interface BatchStats {
|
|
62
|
+
/** Total jobs added */
|
|
63
|
+
totalJobs: number;
|
|
64
|
+
/** Total bytes processed */
|
|
65
|
+
totalBytes: number;
|
|
66
|
+
/** Batches processed */
|
|
67
|
+
batchesProcessed: number;
|
|
68
|
+
/** Average batch size */
|
|
69
|
+
avgBatchSize: number;
|
|
70
|
+
/** Merged jobs count */
|
|
71
|
+
mergedJobs: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Batch events
|
|
76
|
+
*/
|
|
77
|
+
export interface BatchEvents {
|
|
78
|
+
'batch-ready': (data: BatchJob[]) => void;
|
|
79
|
+
'batch-processed': (data: { jobCount: number; bytes: number }) => void;
|
|
80
|
+
'job-added': (data: BatchJob) => void;
|
|
81
|
+
'job-rejected': (data: { reason: string }) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Event handler map type
|
|
86
|
+
*/
|
|
87
|
+
type BatchEventHandlerMap = {
|
|
88
|
+
[K in keyof BatchEvents]: Set<BatchEvents[K]>;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Default batch configuration
|
|
93
|
+
*/
|
|
94
|
+
const DEFAULT_CONFIG: BatchConfig = {
|
|
95
|
+
maxBatchSize: 1024 * 50, // 50KB max per batch
|
|
96
|
+
maxWaitTime: 1000, // 1 second max wait
|
|
97
|
+
minBatchSize: 1, // Process even single jobs
|
|
98
|
+
enableMerging: true, // Enable content merging
|
|
99
|
+
autoProcessInterval: 500, // Check every 500ms
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Batch Print Manager
|
|
104
|
+
*
|
|
105
|
+
* Collects print jobs and processes them in optimized batches.
|
|
106
|
+
* Reduces Bluetooth communication overhead by combining small jobs.
|
|
107
|
+
*/
|
|
108
|
+
export class BatchPrintManager {
|
|
109
|
+
private readonly logger = Logger.scope('BatchPrintManager');
|
|
110
|
+
private readonly jobs: BatchJob[] = [];
|
|
111
|
+
private readonly listeners: BatchEventHandlerMap = {
|
|
112
|
+
'batch-ready': new Set(),
|
|
113
|
+
'batch-processed': new Set(),
|
|
114
|
+
'job-added': new Set(),
|
|
115
|
+
'job-rejected': new Set(),
|
|
116
|
+
};
|
|
117
|
+
private config: BatchConfig;
|
|
118
|
+
private isProcessing = false;
|
|
119
|
+
private waitTimer: ReturnType<typeof setTimeout> | null = null;
|
|
120
|
+
private autoProcessTimer: ReturnType<typeof setInterval> | null = null;
|
|
121
|
+
private stats: BatchStats = {
|
|
122
|
+
totalJobs: 0,
|
|
123
|
+
totalBytes: 0,
|
|
124
|
+
batchesProcessed: 0,
|
|
125
|
+
avgBatchSize: 0,
|
|
126
|
+
mergedJobs: 0,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a new BatchPrintManager instance
|
|
131
|
+
*/
|
|
132
|
+
constructor(config: Partial<BatchConfig> = {}) {
|
|
133
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register event listener
|
|
138
|
+
*/
|
|
139
|
+
on<K extends keyof BatchEvents>(
|
|
140
|
+
event: K,
|
|
141
|
+
callback: BatchEvents[K]
|
|
142
|
+
): void {
|
|
143
|
+
this.listeners[event].add(callback);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Remove event listener
|
|
148
|
+
*/
|
|
149
|
+
off<K extends keyof BatchEvents>(
|
|
150
|
+
event: K,
|
|
151
|
+
callback: BatchEvents[K]
|
|
152
|
+
): void {
|
|
153
|
+
this.listeners[event].delete(callback);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Emit an event
|
|
158
|
+
*/
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
private emit<K extends keyof BatchEvents>(event: K, data: any): void {
|
|
161
|
+
this.listeners[event].forEach(handler => {
|
|
162
|
+
try {
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
(handler as any)(data);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
this.logger.error(`Error in event handler for "${event}":`, error);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Add a job to the batch queue
|
|
173
|
+
*
|
|
174
|
+
* @param data - Print data
|
|
175
|
+
* @param priority - Job priority (higher = more urgent)
|
|
176
|
+
* @param metadata - Optional metadata
|
|
177
|
+
* @returns Job ID
|
|
178
|
+
*/
|
|
179
|
+
addJob(
|
|
180
|
+
data: Uint8Array,
|
|
181
|
+
priority = 1,
|
|
182
|
+
metadata?: Record<string, unknown>
|
|
183
|
+
): string {
|
|
184
|
+
const id = this.generateId();
|
|
185
|
+
const job: BatchJob = {
|
|
186
|
+
id,
|
|
187
|
+
data,
|
|
188
|
+
priority,
|
|
189
|
+
addedAt: Date.now(),
|
|
190
|
+
metadata,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
this.jobs.push(job);
|
|
194
|
+
this.stats.totalJobs++;
|
|
195
|
+
|
|
196
|
+
// Sort by priority (descending)
|
|
197
|
+
this.jobs.sort((a, b) => b.priority - a.priority);
|
|
198
|
+
|
|
199
|
+
this.emit('job-added', job);
|
|
200
|
+
this.logger.debug(`Job added: ${id} (priority: ${priority}, queue size: ${this.jobs.length})`);
|
|
201
|
+
|
|
202
|
+
// Start/restart wait timer
|
|
203
|
+
this.startWaitTimer();
|
|
204
|
+
|
|
205
|
+
// Check if we should process immediately
|
|
206
|
+
if (this.shouldProcessImmediately()) {
|
|
207
|
+
this.emit('batch-ready', [...this.jobs]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Start auto-process if enabled
|
|
211
|
+
this.startAutoProcess();
|
|
212
|
+
|
|
213
|
+
return id;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Add multiple jobs at once
|
|
218
|
+
*/
|
|
219
|
+
addJobs(jobs: Array<{ data: Uint8Array; priority?: number; metadata?: Record<string, unknown> }>): string[] {
|
|
220
|
+
return jobs.map(job => this.addJob(job.data, job.priority, job.metadata));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Cancel a job by ID
|
|
225
|
+
*/
|
|
226
|
+
cancelJob(id: string): boolean {
|
|
227
|
+
const index = this.jobs.findIndex(j => j.id === id);
|
|
228
|
+
if (index === -1) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.jobs.splice(index, 1);
|
|
233
|
+
this.logger.debug(`Job cancelled: ${id}`);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Cancel all jobs
|
|
239
|
+
*/
|
|
240
|
+
cancelAll(): void {
|
|
241
|
+
const count = this.jobs.length;
|
|
242
|
+
this.jobs.length = 0;
|
|
243
|
+
this.clearTimers();
|
|
244
|
+
this.logger.info(`Cancelled ${count} jobs`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get pending job count
|
|
249
|
+
*/
|
|
250
|
+
get pendingCount(): number {
|
|
251
|
+
return this.jobs.length;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get pending jobs
|
|
256
|
+
*/
|
|
257
|
+
getPendingJobs(): BatchJob[] {
|
|
258
|
+
return [...this.jobs];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get current statistics
|
|
263
|
+
*/
|
|
264
|
+
getStats(): BatchStats {
|
|
265
|
+
return { ...this.stats };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Update configuration
|
|
270
|
+
*/
|
|
271
|
+
updateConfig(updates: Partial<BatchConfig>): void {
|
|
272
|
+
this.config = { ...this.config, ...updates };
|
|
273
|
+
this.logger.debug('Configuration updated');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Process the current batch
|
|
278
|
+
*
|
|
279
|
+
* @param processor - Function to send batch data to printer
|
|
280
|
+
* @returns Number of jobs processed
|
|
281
|
+
*/
|
|
282
|
+
async processBatch(
|
|
283
|
+
processor: (data: Uint8Array) => Promise<void>
|
|
284
|
+
): Promise<number> {
|
|
285
|
+
if (this.isProcessing) {
|
|
286
|
+
throw new BluetoothPrintError(
|
|
287
|
+
ErrorCode.PRINT_JOB_IN_PROGRESS,
|
|
288
|
+
'Batch processing already in progress'
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (this.jobs.length === 0) {
|
|
293
|
+
this.logger.debug('No jobs to process');
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
this.isProcessing = true;
|
|
298
|
+
this.clearTimers();
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
// Get jobs for this batch
|
|
302
|
+
const batchJobs = this.prepareBatch();
|
|
303
|
+
const mergedData = this.mergeJobs(batchJobs);
|
|
304
|
+
|
|
305
|
+
this.logger.info(`Processing batch: ${batchJobs.length} jobs, ${mergedData.length} bytes`);
|
|
306
|
+
|
|
307
|
+
// Emit batch ready event
|
|
308
|
+
this.emit('batch-ready', batchJobs);
|
|
309
|
+
|
|
310
|
+
// Process the merged data
|
|
311
|
+
await processor(mergedData);
|
|
312
|
+
|
|
313
|
+
// Update stats
|
|
314
|
+
this.stats.totalBytes += mergedData.length;
|
|
315
|
+
this.stats.batchesProcessed++;
|
|
316
|
+
this.stats.avgBatchSize =
|
|
317
|
+
(this.stats.avgBatchSize * (this.stats.batchesProcessed - 1) + mergedData.length) /
|
|
318
|
+
this.stats.batchesProcessed;
|
|
319
|
+
|
|
320
|
+
// Remove processed jobs
|
|
321
|
+
this.jobs.splice(0, batchJobs.length);
|
|
322
|
+
|
|
323
|
+
this.emit('batch-processed', { jobCount: batchJobs.length, bytes: mergedData.length });
|
|
324
|
+
this.logger.info(`Batch processed: ${batchJobs.length} jobs`);
|
|
325
|
+
|
|
326
|
+
return batchJobs.length;
|
|
327
|
+
} finally {
|
|
328
|
+
this.isProcessing = false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Prepare batch from pending jobs
|
|
334
|
+
*/
|
|
335
|
+
private prepareBatch(): BatchJob[] {
|
|
336
|
+
const batch: BatchJob[] = [];
|
|
337
|
+
let totalSize = 0;
|
|
338
|
+
|
|
339
|
+
for (const job of this.jobs) {
|
|
340
|
+
// Check if adding this job would exceed max batch size
|
|
341
|
+
if (batch.length > 0 && totalSize + job.data.length > this.config.maxBatchSize) {
|
|
342
|
+
// Don't add if it would exceed, and we already have some jobs
|
|
343
|
+
if (batch.length >= this.config.minBatchSize) {
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
batch.push(job);
|
|
349
|
+
totalSize += job.data.length;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return batch;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Merge multiple jobs into a single buffer
|
|
357
|
+
*/
|
|
358
|
+
private mergeJobs(jobs: BatchJob[]): Uint8Array {
|
|
359
|
+
if (!this.config.enableMerging || jobs.length === 1) {
|
|
360
|
+
return jobs[0]?.data ?? new Uint8Array(0);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Calculate total size
|
|
364
|
+
let totalSize = 0;
|
|
365
|
+
for (const job of jobs) {
|
|
366
|
+
totalSize += job.data.length;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Merge into single buffer
|
|
370
|
+
const result = new Uint8Array(totalSize);
|
|
371
|
+
let offset = 0;
|
|
372
|
+
|
|
373
|
+
for (const job of jobs) {
|
|
374
|
+
result.set(job.data, offset);
|
|
375
|
+
offset += job.data.length;
|
|
376
|
+
this.stats.mergedJobs++;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this.logger.debug(`Merged ${jobs.length} jobs into ${totalSize} bytes`);
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Check if we should process immediately
|
|
385
|
+
*/
|
|
386
|
+
private shouldProcessImmediately(): boolean {
|
|
387
|
+
if (this.jobs.length === 0) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Large single job
|
|
392
|
+
const firstJob = this.jobs[0];
|
|
393
|
+
if (this.jobs.length === 1 && firstJob && firstJob.data.length >= this.config.maxBatchSize * 0.8) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Queue is full
|
|
398
|
+
const totalSize = this.jobs.reduce((sum, j) => sum + j.data.length, 0);
|
|
399
|
+
if (totalSize >= this.config.maxBatchSize) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Start the wait timer
|
|
408
|
+
*/
|
|
409
|
+
private startWaitTimer(): void {
|
|
410
|
+
this.clearWaitTimer();
|
|
411
|
+
|
|
412
|
+
if (this.config.maxWaitTime <= 0) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
this.waitTimer = setTimeout(() => {
|
|
417
|
+
this.logger.debug('Wait timer expired, batch ready');
|
|
418
|
+
this.emit('batch-ready', [...this.jobs]);
|
|
419
|
+
}, this.config.maxWaitTime);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Clear wait timer
|
|
424
|
+
*/
|
|
425
|
+
private clearWaitTimer(): void {
|
|
426
|
+
if (this.waitTimer) {
|
|
427
|
+
clearTimeout(this.waitTimer);
|
|
428
|
+
this.waitTimer = null;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Start auto-process timer
|
|
434
|
+
*/
|
|
435
|
+
private startAutoProcess(): void {
|
|
436
|
+
if (this.autoProcessTimer || this.config.autoProcessInterval <= 0) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
this.autoProcessTimer = setInterval(() => {
|
|
441
|
+
// Check if batch is ready
|
|
442
|
+
if (this.shouldProcessImmediately()) {
|
|
443
|
+
this.emit('batch-ready', [...this.jobs]);
|
|
444
|
+
}
|
|
445
|
+
}, this.config.autoProcessInterval);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Stop auto-process timer
|
|
450
|
+
*/
|
|
451
|
+
private stopAutoProcess(): void {
|
|
452
|
+
if (this.autoProcessTimer) {
|
|
453
|
+
clearInterval(this.autoProcessTimer);
|
|
454
|
+
this.autoProcessTimer = null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Clear all timers
|
|
460
|
+
*/
|
|
461
|
+
private clearTimers(): void {
|
|
462
|
+
this.clearWaitTimer();
|
|
463
|
+
this.stopAutoProcess();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Generate unique job ID
|
|
468
|
+
*/
|
|
469
|
+
private generateId(): string {
|
|
470
|
+
return `batch_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Reset statistics
|
|
475
|
+
*/
|
|
476
|
+
resetStats(): void {
|
|
477
|
+
this.stats = {
|
|
478
|
+
totalJobs: 0,
|
|
479
|
+
totalBytes: 0,
|
|
480
|
+
batchesProcessed: 0,
|
|
481
|
+
avgBatchSize: 0,
|
|
482
|
+
mergedJobs: 0,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Destroy the manager
|
|
488
|
+
*/
|
|
489
|
+
destroy(): void {
|
|
490
|
+
this.cancelAll();
|
|
491
|
+
// Clear all listeners
|
|
492
|
+
for (const key of Object.keys(this.listeners) as (keyof BatchEventHandlerMap)[]) {
|
|
493
|
+
this.listeners[key].clear();
|
|
494
|
+
}
|
|
495
|
+
this.logger.info('BatchPrintManager destroyed');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Export singleton for convenience
|
|
500
|
+
export const batchPrintManager = new BatchPrintManager();
|
|
@@ -71,7 +71,7 @@ export class ConnectionManager
|
|
|
71
71
|
private adapter: IPrinterAdapter;
|
|
72
72
|
private deviceId: string | null = null;
|
|
73
73
|
private state: PrinterState = PrinterState.DISCONNECTED;
|
|
74
|
-
private readonly
|
|
74
|
+
private readonly connLogger = Logger.scope('ConnectionManager');
|
|
75
75
|
private readonly config: Required<ConnectionManagerConfig>;
|
|
76
76
|
|
|
77
77
|
// Heartbeat state
|
|
@@ -102,7 +102,7 @@ export class ConnectionManager
|
|
|
102
102
|
private handleStateChange(newState: PrinterState): void {
|
|
103
103
|
const previousState = this.state;
|
|
104
104
|
this.state = newState;
|
|
105
|
-
this.
|
|
105
|
+
this.connLogger.debug('State changed:', { from: previousState, to: newState });
|
|
106
106
|
this.emit('state-change', newState);
|
|
107
107
|
|
|
108
108
|
// Handle unexpected disconnection
|
|
@@ -112,7 +112,7 @@ export class ConnectionManager
|
|
|
112
112
|
this.deviceId &&
|
|
113
113
|
!this.isReconnecting
|
|
114
114
|
) {
|
|
115
|
-
this.
|
|
115
|
+
this.connLogger.warn('Unexpected disconnection detected');
|
|
116
116
|
this.emit('disconnected', this.deviceId);
|
|
117
117
|
this.stopHeartbeat();
|
|
118
118
|
|
|
@@ -127,7 +127,7 @@ export class ConnectionManager
|
|
|
127
127
|
* Connects to a Bluetooth device
|
|
128
128
|
*/
|
|
129
129
|
async connect(deviceId: string, options?: { retries?: number; timeout?: number }): Promise<void> {
|
|
130
|
-
this.
|
|
130
|
+
this.connLogger.info('Connecting to device:', deviceId);
|
|
131
131
|
|
|
132
132
|
const { retries = 0, timeout = this.config.connectionTimeout } = options || {};
|
|
133
133
|
let attempts = 0;
|
|
@@ -159,7 +159,7 @@ export class ConnectionManager
|
|
|
159
159
|
this.state = PrinterState.CONNECTED;
|
|
160
160
|
this.emit('state-change', PrinterState.CONNECTED);
|
|
161
161
|
this.emit('connected', deviceId);
|
|
162
|
-
this.
|
|
162
|
+
this.connLogger.info('Connected successfully');
|
|
163
163
|
|
|
164
164
|
// Start heartbeat if enabled
|
|
165
165
|
if (this.config.heartbeatEnabled) {
|
|
@@ -181,11 +181,14 @@ export class ConnectionManager
|
|
|
181
181
|
`Connection failed after ${attempts} attempts`,
|
|
182
182
|
error as Error
|
|
183
183
|
);
|
|
184
|
-
this.
|
|
184
|
+
this.connLogger.error('Connection failed:', printError);
|
|
185
185
|
this.emit('error', printError);
|
|
186
186
|
throw printError;
|
|
187
187
|
}
|
|
188
|
-
this.
|
|
188
|
+
this.connLogger.warn(
|
|
189
|
+
`Connection attempt ${attempts}/${retries} failed, retrying...`,
|
|
190
|
+
error
|
|
191
|
+
);
|
|
189
192
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
190
193
|
}
|
|
191
194
|
}
|
|
@@ -200,12 +203,12 @@ export class ConnectionManager
|
|
|
200
203
|
this.isReconnecting = false;
|
|
201
204
|
|
|
202
205
|
if (!this.deviceId) {
|
|
203
|
-
this.
|
|
206
|
+
this.connLogger.warn('Disconnect called but no device connected');
|
|
204
207
|
return;
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
const deviceId = this.deviceId;
|
|
208
|
-
this.
|
|
211
|
+
this.connLogger.info('Disconnecting from device:', deviceId);
|
|
209
212
|
|
|
210
213
|
try {
|
|
211
214
|
await this.adapter.disconnect(deviceId);
|
|
@@ -213,14 +216,14 @@ export class ConnectionManager
|
|
|
213
216
|
this.state = PrinterState.DISCONNECTED;
|
|
214
217
|
this.emit('state-change', PrinterState.DISCONNECTED);
|
|
215
218
|
this.emit('disconnected', deviceId);
|
|
216
|
-
this.
|
|
219
|
+
this.connLogger.info('Disconnected successfully');
|
|
217
220
|
} catch (error) {
|
|
218
221
|
const printError = new BluetoothPrintError(
|
|
219
222
|
ErrorCode.DEVICE_DISCONNECTED,
|
|
220
223
|
'Disconnect failed',
|
|
221
224
|
error as Error
|
|
222
225
|
);
|
|
223
|
-
this.
|
|
226
|
+
this.connLogger.error('Disconnect failed:', printError);
|
|
224
227
|
this.emit('error', printError);
|
|
225
228
|
throw printError;
|
|
226
229
|
}
|
|
@@ -236,7 +239,7 @@ export class ConnectionManager
|
|
|
236
239
|
this.checkHeartbeat();
|
|
237
240
|
}, this.config.heartbeatInterval);
|
|
238
241
|
|
|
239
|
-
this.
|
|
242
|
+
this.connLogger.debug('Heartbeat started with interval:', this.config.heartbeatInterval);
|
|
240
243
|
}
|
|
241
244
|
|
|
242
245
|
/**
|
|
@@ -246,7 +249,7 @@ export class ConnectionManager
|
|
|
246
249
|
if (this.heartbeatTimer) {
|
|
247
250
|
clearInterval(this.heartbeatTimer);
|
|
248
251
|
this.heartbeatTimer = null;
|
|
249
|
-
this.
|
|
252
|
+
this.connLogger.debug('Heartbeat stopped');
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -262,12 +265,12 @@ export class ConnectionManager
|
|
|
262
265
|
const isConnected = this.isConnected();
|
|
263
266
|
|
|
264
267
|
if (isConnected) {
|
|
265
|
-
this.
|
|
268
|
+
this.connLogger.debug('Heartbeat OK');
|
|
266
269
|
} else {
|
|
267
270
|
this.handleHeartbeatLost();
|
|
268
271
|
}
|
|
269
272
|
} catch (error) {
|
|
270
|
-
this.
|
|
273
|
+
this.connLogger.warn('Heartbeat check failed:', error);
|
|
271
274
|
this.handleHeartbeatLost();
|
|
272
275
|
}
|
|
273
276
|
}
|
|
@@ -278,7 +281,7 @@ export class ConnectionManager
|
|
|
278
281
|
private handleHeartbeatLost(): void {
|
|
279
282
|
if (!this.deviceId) return;
|
|
280
283
|
|
|
281
|
-
this.
|
|
284
|
+
this.connLogger.warn('Heartbeat lost for device:', this.deviceId);
|
|
282
285
|
this.emit('heartbeat-lost', this.deviceId);
|
|
283
286
|
this.stopHeartbeat();
|
|
284
287
|
|
|
@@ -317,7 +320,7 @@ export class ConnectionManager
|
|
|
317
320
|
const deviceId = this.deviceId;
|
|
318
321
|
|
|
319
322
|
if (this.reconnectAttempts > this.config.maxReconnectAttempts) {
|
|
320
|
-
this.
|
|
323
|
+
this.connLogger.error('Max reconnect attempts reached');
|
|
321
324
|
this.isReconnecting = false;
|
|
322
325
|
this.emit('reconnect-failed', {
|
|
323
326
|
deviceId,
|
|
@@ -330,7 +333,7 @@ export class ConnectionManager
|
|
|
330
333
|
return;
|
|
331
334
|
}
|
|
332
335
|
|
|
333
|
-
this.
|
|
336
|
+
this.connLogger.info(
|
|
334
337
|
`Reconnect attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`
|
|
335
338
|
);
|
|
336
339
|
this.emit('reconnecting', {
|
|
@@ -345,7 +348,7 @@ export class ConnectionManager
|
|
|
345
348
|
this.adapter
|
|
346
349
|
.connect(deviceId)
|
|
347
350
|
.then(() => {
|
|
348
|
-
this.
|
|
351
|
+
this.connLogger.info('Reconnected successfully');
|
|
349
352
|
this.isReconnecting = false;
|
|
350
353
|
this.reconnectAttempts = 0;
|
|
351
354
|
this.state = PrinterState.CONNECTED;
|
|
@@ -357,7 +360,7 @@ export class ConnectionManager
|
|
|
357
360
|
}
|
|
358
361
|
})
|
|
359
362
|
.catch(error => {
|
|
360
|
-
this.
|
|
363
|
+
this.connLogger.warn(`Reconnect attempt ${this.reconnectAttempts} failed:`, error);
|
|
361
364
|
|
|
362
365
|
this.reconnectTimer = setTimeout(() => {
|
|
363
366
|
this.attemptReconnect();
|
|
@@ -434,7 +437,7 @@ export class ConnectionManager
|
|
|
434
437
|
}
|
|
435
438
|
|
|
436
439
|
if (this.isReconnecting) {
|
|
437
|
-
this.
|
|
440
|
+
this.connLogger.warn('Reconnect already in progress');
|
|
438
441
|
return;
|
|
439
442
|
}
|
|
440
443
|
|
|
@@ -448,7 +451,7 @@ export class ConnectionManager
|
|
|
448
451
|
this.clearReconnectTimer();
|
|
449
452
|
this.isReconnecting = false;
|
|
450
453
|
this.reconnectAttempts = 0;
|
|
451
|
-
this.
|
|
454
|
+
this.connLogger.info('Reconnect stopped');
|
|
452
455
|
}
|
|
453
456
|
|
|
454
457
|
/**
|