taro-bluetooth-print 2.10.2 → 2.11.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 +19 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/adapters/ChunkWriteStrategy.d.ts +12 -0
- package/dist/types/adapters/ReactNativeAdapter.d.ts +1 -1
- package/dist/types/errors/baseError.d.ts +4 -1
- package/dist/types/services/PrintHistory.d.ts +2 -0
- package/dist/types/template/TemplateEngine.d.ts +2 -0
- package/dist/types/template/engines/TemplateRenderer.d.ts +2 -0
- package/package.json +1 -1
- package/src/adapters/ChunkWriteStrategy.ts +30 -5
- package/src/adapters/ReactNativeAdapter.ts +3 -2
- package/src/config/PrinterConfigManager.ts +2 -1
- package/src/device/DeviceManager.ts +7 -2
- package/src/device/DiscoveryService.ts +0 -4
- package/src/errors/baseError.ts +7 -0
- package/src/factory/PrinterFactory.ts +3 -1
- package/src/preview/PreviewRenderer.ts +3 -2
- package/src/queue/PrintQueue.ts +2 -1
- package/src/services/CloudPrintManager.ts +2 -1
- package/src/services/PrintHistory.ts +4 -1
- package/src/services/PrintJobManager.ts +4 -1
- package/src/services/PrintScheduler.ts +14 -4
- package/src/template/TemplateEngine.ts +4 -1
- package/src/template/engines/TemplateRenderer.ts +5 -7
- package/src/utils/image.ts +4 -1
- package/src/utils/outputLimiter.ts +7 -4
|
@@ -75,6 +75,18 @@ export declare abstract class ChunkWriteStrategy<TOptions = void> {
|
|
|
75
75
|
* Default: no-op. Override for platforms that support connection state polling.
|
|
76
76
|
*/
|
|
77
77
|
protected checkConnection(_deviceId: string): Promise<void>;
|
|
78
|
+
/** Step size for increasing chunk size on success (bytes) */
|
|
79
|
+
private static readonly CHUNK_SIZE_STEP;
|
|
80
|
+
/** Delay backoff multiplier on failure */
|
|
81
|
+
private static readonly DELAY_BACKOFF_FACTOR;
|
|
82
|
+
/** Delay recovery divisor on success */
|
|
83
|
+
private static readonly DELAY_RECOVERY_FACTOR;
|
|
84
|
+
/** Timeout base (ms) for computeTimeoutMs */
|
|
85
|
+
private static readonly TIMEOUT_BASE_MS;
|
|
86
|
+
/** Timeout per-byte factor (ms) for computeTimeoutMs */
|
|
87
|
+
private static readonly TIMEOUT_PER_BYTE_MS;
|
|
88
|
+
/** Maximum timeout (ms) for computeTimeoutMs */
|
|
89
|
+
private static readonly TIMEOUT_MAX_MS;
|
|
78
90
|
/**
|
|
79
91
|
* Compute chunk write timeout based on chunk size.
|
|
80
92
|
* Override to customize timeout formula per platform.
|
|
@@ -46,7 +46,7 @@ export declare class ReactNativeAdapter extends BaseAdapter implements IPrinterA
|
|
|
46
46
|
*
|
|
47
47
|
* @param options - Configuration options
|
|
48
48
|
* @param options.bleManager - BLE Manager instance (e.g., from react-native-ble-plx)
|
|
49
|
-
* @throws {
|
|
49
|
+
* @throws {BluetoothPrintError} If bleManager is not provided or not supported
|
|
50
50
|
*/
|
|
51
51
|
constructor(options: {
|
|
52
52
|
bleManager: BLEManager;
|
|
@@ -18,7 +18,10 @@ export declare enum ErrorCode {
|
|
|
18
18
|
ENCODING_NOT_SUPPORTED = "ENCODING_NOT_SUPPORTED",
|
|
19
19
|
INVALID_IMAGE_DATA = "INVALID_IMAGE_DATA",
|
|
20
20
|
INVALID_QR_DATA = "INVALID_QR_DATA",
|
|
21
|
-
PLATFORM_NOT_SUPPORTED = "PLATFORM_NOT_SUPPORTED"
|
|
21
|
+
PLATFORM_NOT_SUPPORTED = "PLATFORM_NOT_SUPPORTED",
|
|
22
|
+
QUEUE_FULL = "QUEUE_FULL",
|
|
23
|
+
QUEUE_JOB_NOT_FOUND = "QUEUE_JOB_NOT_FOUND",
|
|
24
|
+
PREVIEW_FAILED = "PREVIEW_FAILED"
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Custom error class for Bluetooth printing operations
|
|
@@ -73,6 +73,8 @@ export declare class PrintHistory {
|
|
|
73
73
|
private readonly logger;
|
|
74
74
|
private readonly entries;
|
|
75
75
|
private counter;
|
|
76
|
+
/** Default maximum number of history entries */
|
|
77
|
+
private static readonly DEFAULT_MAX_ENTRIES;
|
|
76
78
|
private readonly maxEntries;
|
|
77
79
|
/**
|
|
78
80
|
* Creates a new PrintHistory instance
|
|
@@ -268,6 +268,8 @@ export declare class TemplateEngine implements ITemplateEngine {
|
|
|
268
268
|
private readonly parser;
|
|
269
269
|
private readonly renderer;
|
|
270
270
|
private readonly templates;
|
|
271
|
+
/** Default paper width in characters (58mm paper ≈ 48 chars) */
|
|
272
|
+
private static readonly DEFAULT_PAPER_WIDTH;
|
|
271
273
|
/**
|
|
272
274
|
* Creates a new TemplateEngine instance
|
|
273
275
|
*/
|
|
@@ -11,6 +11,8 @@ export declare class TemplateRenderer {
|
|
|
11
11
|
private readonly driver;
|
|
12
12
|
private readonly parser;
|
|
13
13
|
private readonly paperWidth;
|
|
14
|
+
/** Default paper width in characters (58mm paper ≈ 48 chars) */
|
|
15
|
+
private static readonly DEFAULT_PAPER_WIDTH;
|
|
14
16
|
constructor(paperWidth?: number);
|
|
15
17
|
/**
|
|
16
18
|
* Render a receipt template
|
package/package.json
CHANGED
|
@@ -116,12 +116,31 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
|
|
|
116
116
|
// default: no connection check
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/** Step size for increasing chunk size on success (bytes) */
|
|
120
|
+
private static readonly CHUNK_SIZE_STEP = 5;
|
|
121
|
+
/** Delay backoff multiplier on failure */
|
|
122
|
+
private static readonly DELAY_BACKOFF_FACTOR = 1.5;
|
|
123
|
+
/** Delay recovery divisor on success */
|
|
124
|
+
private static readonly DELAY_RECOVERY_FACTOR = 1.2;
|
|
125
|
+
/** Timeout base (ms) for computeTimeoutMs */
|
|
126
|
+
private static readonly TIMEOUT_BASE_MS = 1000;
|
|
127
|
+
/** Timeout per-byte factor (ms) for computeTimeoutMs */
|
|
128
|
+
private static readonly TIMEOUT_PER_BYTE_MS = 5;
|
|
129
|
+
/** Maximum timeout (ms) for computeTimeoutMs */
|
|
130
|
+
private static readonly TIMEOUT_MAX_MS = 10000;
|
|
131
|
+
|
|
119
132
|
/**
|
|
120
133
|
* Compute chunk write timeout based on chunk size.
|
|
121
134
|
* Override to customize timeout formula per platform.
|
|
122
135
|
*/
|
|
123
136
|
protected computeTimeoutMs(chunkLength: number): number {
|
|
124
|
-
return Math.max(
|
|
137
|
+
return Math.max(
|
|
138
|
+
ChunkWriteStrategy.TIMEOUT_BASE_MS,
|
|
139
|
+
Math.min(
|
|
140
|
+
ChunkWriteStrategy.TIMEOUT_MAX_MS,
|
|
141
|
+
ChunkWriteStrategy.TIMEOUT_BASE_MS + chunkLength * ChunkWriteStrategy.TIMEOUT_PER_BYTE_MS
|
|
142
|
+
)
|
|
143
|
+
);
|
|
125
144
|
}
|
|
126
145
|
|
|
127
146
|
/**
|
|
@@ -218,8 +237,11 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
|
|
|
218
237
|
consecutiveFailures = 0;
|
|
219
238
|
|
|
220
239
|
if (successCount % successThreshold === 0 && currentChunkSize < maxChunkSize) {
|
|
221
|
-
currentChunkSize = Math.min(
|
|
222
|
-
|
|
240
|
+
currentChunkSize = Math.min(
|
|
241
|
+
maxChunkSize,
|
|
242
|
+
currentChunkSize + ChunkWriteStrategy.CHUNK_SIZE_STEP
|
|
243
|
+
);
|
|
244
|
+
baseDelay = Math.max(baseDelay / ChunkWriteStrategy.DELAY_RECOVERY_FACTOR, delay);
|
|
223
245
|
totalChunks =
|
|
224
246
|
Math.ceil((data.length - i - currentChunkSize) / currentChunkSize) + chunkNum;
|
|
225
247
|
this.logger.debug(`Increased chunk size to ${currentChunkSize}, delay to ${baseDelay}`);
|
|
@@ -228,8 +250,11 @@ export abstract class ChunkWriteStrategy<TOptions = void> {
|
|
|
228
250
|
consecutiveFailures++;
|
|
229
251
|
|
|
230
252
|
if (consecutiveFailures >= failureThreshold && currentChunkSize > minChunkSize) {
|
|
231
|
-
currentChunkSize = Math.max(
|
|
232
|
-
|
|
253
|
+
currentChunkSize = Math.max(
|
|
254
|
+
minChunkSize,
|
|
255
|
+
currentChunkSize - ChunkWriteStrategy.CHUNK_SIZE_STEP
|
|
256
|
+
);
|
|
257
|
+
baseDelay = Math.min(baseDelay * ChunkWriteStrategy.DELAY_BACKOFF_FACTOR, maxDelay);
|
|
233
258
|
totalChunks =
|
|
234
259
|
Math.ceil((data.length - i - currentChunkSize) / currentChunkSize) + chunkNum;
|
|
235
260
|
this.logger.debug(`Decreased chunk size to ${currentChunkSize}, delay to ${baseDelay}`);
|
|
@@ -193,13 +193,14 @@ export class ReactNativeAdapter extends BaseAdapter implements IPrinterAdapter {
|
|
|
193
193
|
*
|
|
194
194
|
* @param options - Configuration options
|
|
195
195
|
* @param options.bleManager - BLE Manager instance (e.g., from react-native-ble-plx)
|
|
196
|
-
* @throws {
|
|
196
|
+
* @throws {BluetoothPrintError} If bleManager is not provided or not supported
|
|
197
197
|
*/
|
|
198
198
|
constructor(options: { bleManager: BLEManager }) {
|
|
199
199
|
super();
|
|
200
200
|
|
|
201
201
|
if (!options?.bleManager) {
|
|
202
|
-
throw new
|
|
202
|
+
throw new BluetoothPrintError(
|
|
203
|
+
ErrorCode.INVALID_CONFIGURATION,
|
|
203
204
|
'ReactNativeAdapter requires a bleManager instance (e.g., react-native-ble-plx). ' +
|
|
204
205
|
'Please pass { bleManager: yourBleManager } in the constructor.'
|
|
205
206
|
);
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { Logger } from '@/utils/logger';
|
|
32
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Print configuration for a device
|
|
@@ -338,7 +339,7 @@ export class PrinterConfigManager {
|
|
|
338
339
|
setDefaultPrinter(id: string): void {
|
|
339
340
|
const printer = this.printers.get(id);
|
|
340
341
|
if (!printer) {
|
|
341
|
-
throw new
|
|
342
|
+
throw new BluetoothPrintError(ErrorCode.DEVICE_NOT_FOUND, `Printer not found: ${id}`);
|
|
342
343
|
}
|
|
343
344
|
|
|
344
345
|
// Unset other defaults
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import Taro from '@tarojs/taro';
|
|
16
|
+
|
|
17
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
16
18
|
import { Logger } from '@/utils/logger';
|
|
17
19
|
import { emitAndThrow } from '@/utils/normalizeError';
|
|
18
20
|
import { EventEmitter } from '@/core/EventEmitter';
|
|
@@ -214,7 +216,7 @@ export class DeviceManager extends EventEmitter<DeviceManagerEvents> implements
|
|
|
214
216
|
const services = servicesRes.services;
|
|
215
217
|
|
|
216
218
|
if (services.length === 0) {
|
|
217
|
-
throw new
|
|
219
|
+
throw new BluetoothPrintError(ErrorCode.SERVICE_NOT_FOUND, 'No services found on device');
|
|
218
220
|
}
|
|
219
221
|
|
|
220
222
|
let serviceId: string | undefined;
|
|
@@ -238,7 +240,10 @@ export class DeviceManager extends EventEmitter<DeviceManagerEvents> implements
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
if (!serviceId || !characteristicId) {
|
|
241
|
-
throw new
|
|
243
|
+
throw new BluetoothPrintError(
|
|
244
|
+
ErrorCode.CHARACTERISTIC_NOT_FOUND,
|
|
245
|
+
'No writable characteristic found'
|
|
246
|
+
);
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
const device = this.discoveredDevices.get(deviceId);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
|
|
2
1
|
/**
|
|
3
2
|
* @fileoverview DiscoveryService uses dynamic adapter loading for platform-specific Bluetooth operations.
|
|
4
3
|
* The platformAdapter field is typed as `BaseAdapter | undefined` because different adapters
|
|
@@ -216,9 +215,6 @@ export class DiscoveryService extends EventEmitter<DiscoveryEvents> {
|
|
|
216
215
|
}
|
|
217
216
|
}
|
|
218
217
|
|
|
219
|
-
// 监听 platform adapter 的设备发现回调(预留扩展点)
|
|
220
|
-
// private processDevice(deviceInfo: any): void { ... }
|
|
221
|
-
|
|
222
218
|
/**
|
|
223
219
|
* 获取过滤和排序后的设备列表
|
|
224
220
|
*/
|
package/src/errors/baseError.ts
CHANGED
|
@@ -32,6 +32,13 @@ export enum ErrorCode {
|
|
|
32
32
|
|
|
33
33
|
// Platform errors
|
|
34
34
|
PLATFORM_NOT_SUPPORTED = 'PLATFORM_NOT_SUPPORTED',
|
|
35
|
+
|
|
36
|
+
// Queue errors
|
|
37
|
+
QUEUE_FULL = 'QUEUE_FULL',
|
|
38
|
+
QUEUE_JOB_NOT_FOUND = 'QUEUE_JOB_NOT_FOUND',
|
|
39
|
+
|
|
40
|
+
// Preview errors
|
|
41
|
+
PREVIEW_FAILED = 'PREVIEW_FAILED',
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
/**
|
|
@@ -12,6 +12,7 @@ import { PrintJobManager } from '@/services/PrintJobManager';
|
|
|
12
12
|
import { CommandBuilder } from '@/services/CommandBuilder';
|
|
13
13
|
import type { IPrinterAdapter } from '@/types';
|
|
14
14
|
import type { IConnectionManager, IPrintJobManager, ICommandBuilder } from '@/services/interfaces';
|
|
15
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Options for creating a BluetoothPrinter via the factory
|
|
@@ -127,7 +128,8 @@ export async function createWebBluetoothPrinter(): Promise<BluetoothPrinter> {
|
|
|
127
128
|
const adapter = new WebBluetoothAdapter();
|
|
128
129
|
return createBluetoothPrinter({ adapter });
|
|
129
130
|
} catch {
|
|
130
|
-
throw new
|
|
131
|
+
throw new BluetoothPrintError(
|
|
132
|
+
ErrorCode.PLATFORM_NOT_SUPPORTED,
|
|
131
133
|
'Failed to dynamically import WebBluetoothAdapter. ' +
|
|
132
134
|
'This adapter is only available in browser environments that support the Web Bluetooth API.'
|
|
133
135
|
);
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { Logger } from '@/utils/logger';
|
|
16
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Preview options
|
|
@@ -141,7 +142,7 @@ export class PreviewRenderer implements IPreviewRenderer {
|
|
|
141
142
|
|
|
142
143
|
const ctx = canvas.getContext('2d');
|
|
143
144
|
if (!ctx) {
|
|
144
|
-
throw new
|
|
145
|
+
throw new BluetoothPrintError(ErrorCode.PREVIEW_FAILED, 'Failed to get canvas context');
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
this.drawToContext(ctx, lines, pixelWidth, estimatedHeight, opts);
|
|
@@ -415,7 +416,7 @@ export class PreviewRenderer implements IPreviewRenderer {
|
|
|
415
416
|
|
|
416
417
|
const ctx = canvas.getContext('2d');
|
|
417
418
|
if (!ctx) {
|
|
418
|
-
throw new
|
|
419
|
+
throw new BluetoothPrintError(ErrorCode.PREVIEW_FAILED, 'Failed to get canvas context');
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
this.drawToContext(ctx, lines, width, height, opts);
|
package/src/queue/PrintQueue.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { Logger } from '@/utils/logger';
|
|
16
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
16
17
|
import { normalizeError } from '@/utils/normalizeError';
|
|
17
18
|
import { EventEmitter } from '@/core/EventEmitter';
|
|
18
19
|
|
|
@@ -170,7 +171,7 @@ export class PrintQueue extends EventEmitter<PrintQueueEvents> implements IPrint
|
|
|
170
171
|
*/
|
|
171
172
|
add(data: Uint8Array, options?: Partial<PrintJob>): string {
|
|
172
173
|
if (this.jobs.size >= this.config.maxSize) {
|
|
173
|
-
throw new
|
|
174
|
+
throw new BluetoothPrintError(ErrorCode.QUEUE_FULL, 'Queue is full');
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
const id = this.generateJobId();
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { EventEmitter } from '@/core/EventEmitter';
|
|
14
14
|
import { Logger } from '@/utils/logger';
|
|
15
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
15
16
|
import { normalizeError } from '@/utils/normalizeError';
|
|
16
17
|
|
|
17
18
|
export interface CloudPrintOptions {
|
|
@@ -233,7 +234,7 @@ export class CloudPrintManager extends EventEmitter<CloudPrintEvents> {
|
|
|
233
234
|
*/
|
|
234
235
|
print(job: PrintJob): void {
|
|
235
236
|
if (!this.isConnected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
236
|
-
throw new
|
|
237
|
+
throw new BluetoothPrintError(ErrorCode.DEVICE_DISCONNECTED, 'Not connected to server');
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
const message = {
|
|
@@ -92,13 +92,16 @@ export class PrintHistory {
|
|
|
92
92
|
private readonly logger = Logger.scope('PrintHistory');
|
|
93
93
|
private readonly entries: Map<string, PrintHistoryEntry> = new Map();
|
|
94
94
|
private counter = 0;
|
|
95
|
+
/** Default maximum number of history entries */
|
|
96
|
+
private static readonly DEFAULT_MAX_ENTRIES = 1000;
|
|
97
|
+
|
|
95
98
|
private readonly maxEntries: number;
|
|
96
99
|
|
|
97
100
|
/**
|
|
98
101
|
* Creates a new PrintHistory instance
|
|
99
102
|
* @param maxEntries - Maximum number of entries to keep (default: 1000)
|
|
100
103
|
*/
|
|
101
|
-
constructor(maxEntries =
|
|
104
|
+
constructor(maxEntries = PrintHistory.DEFAULT_MAX_ENTRIES) {
|
|
102
105
|
this.maxEntries = maxEntries;
|
|
103
106
|
}
|
|
104
107
|
|
|
@@ -297,7 +297,10 @@ export class PrintJobManager implements IPrintJobManager {
|
|
|
297
297
|
`Loaded job ${this.jobId}: offset=${this.jobOffset}/${this.jobBuffer.length}`
|
|
298
298
|
);
|
|
299
299
|
} else {
|
|
300
|
-
throw new
|
|
300
|
+
throw new BluetoothPrintError(
|
|
301
|
+
ErrorCode.QUEUE_JOB_NOT_FOUND,
|
|
302
|
+
`Job state not found for ${jobId}`
|
|
303
|
+
);
|
|
301
304
|
}
|
|
302
305
|
} catch (error) {
|
|
303
306
|
this.logger.error(`Failed to load job state for ${jobId}:`, error);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { EventEmitter } from '../core/EventEmitter';
|
|
7
7
|
import { generateUUID } from '../utils/uuid';
|
|
8
8
|
import { normalizeError } from '../utils/normalizeError';
|
|
9
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
9
10
|
|
|
10
11
|
export interface ScheduledPrint {
|
|
11
12
|
id: string;
|
|
@@ -57,7 +58,7 @@ export function parseCronExpression(cron: string): {
|
|
|
57
58
|
} {
|
|
58
59
|
const parts = cron.trim().split(/\s+/);
|
|
59
60
|
if (parts.length !== 5) {
|
|
60
|
-
throw new
|
|
61
|
+
throw new BluetoothPrintError(ErrorCode.INVALID_CONFIGURATION, 'Invalid cron expression');
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
const [minPart, hourPart, dayPart, monthPart, dowPart] = parts as [
|
|
@@ -143,7 +144,10 @@ export function getNextCronRun(cron: string, fromTime: number = Date.now()): num
|
|
|
143
144
|
date.setMinutes(date.getMinutes() + 1);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
throw new
|
|
147
|
+
throw new BluetoothPrintError(
|
|
148
|
+
ErrorCode.INVALID_CONFIGURATION,
|
|
149
|
+
'Cannot find next run time within one year'
|
|
150
|
+
);
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
export function getNextIntervalRun(interval: number, fromTime: number = Date.now()): number {
|
|
@@ -173,7 +177,10 @@ export class PrintScheduler extends EventEmitter<ScheduleEvents> {
|
|
|
173
177
|
const executeAt = onceAt instanceof Date ? onceAt.getTime() : onceAt;
|
|
174
178
|
|
|
175
179
|
if (!executeAt || executeAt <= Date.now()) {
|
|
176
|
-
throw new
|
|
180
|
+
throw new BluetoothPrintError(
|
|
181
|
+
ErrorCode.INVALID_CONFIGURATION,
|
|
182
|
+
'onceAt must be a future date'
|
|
183
|
+
);
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
const job: ScheduledPrint = {
|
|
@@ -198,7 +205,10 @@ export class PrintScheduler extends EventEmitter<ScheduleEvents> {
|
|
|
198
205
|
const { cronExpression, repeatInterval, ...rest } = options;
|
|
199
206
|
|
|
200
207
|
if (!cronExpression && !repeatInterval) {
|
|
201
|
-
throw new
|
|
208
|
+
throw new BluetoothPrintError(
|
|
209
|
+
ErrorCode.INVALID_CONFIGURATION,
|
|
210
|
+
'Either cronExpression or repeatInterval is required'
|
|
211
|
+
);
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
const job: ScheduledPrint = {
|
|
@@ -293,10 +293,13 @@ export class TemplateEngine implements ITemplateEngine {
|
|
|
293
293
|
private readonly renderer: TemplateRenderer;
|
|
294
294
|
private readonly templates: Map<string, TemplateDefinition> = new Map();
|
|
295
295
|
|
|
296
|
+
/** Default paper width in characters (58mm paper ≈ 48 chars) */
|
|
297
|
+
private static readonly DEFAULT_PAPER_WIDTH = 48;
|
|
298
|
+
|
|
296
299
|
/**
|
|
297
300
|
* Creates a new TemplateEngine instance
|
|
298
301
|
*/
|
|
299
|
-
constructor(paperWidth =
|
|
302
|
+
constructor(paperWidth = TemplateEngine.DEFAULT_PAPER_WIDTH) {
|
|
300
303
|
this.parser = new TemplateParser();
|
|
301
304
|
this.renderer = new TemplateRenderer(paperWidth);
|
|
302
305
|
}
|
|
@@ -118,7 +118,10 @@ export class TemplateRenderer {
|
|
|
118
118
|
private readonly parser: TemplateParser;
|
|
119
119
|
private readonly paperWidth: number;
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
/** Default paper width in characters (58mm paper ≈ 48 chars) */
|
|
122
|
+
private static readonly DEFAULT_PAPER_WIDTH = 48;
|
|
123
|
+
|
|
124
|
+
constructor(paperWidth = TemplateRenderer.DEFAULT_PAPER_WIDTH) {
|
|
122
125
|
this.paperWidth = paperWidth;
|
|
123
126
|
this.formatter = new TextFormatter();
|
|
124
127
|
this.barcodeGenerator = new BarcodeGenerator();
|
|
@@ -363,21 +366,16 @@ export class TemplateRenderer {
|
|
|
363
366
|
}
|
|
364
367
|
|
|
365
368
|
for (let i = 0; i < items.length; i++) {
|
|
366
|
-
|
|
367
|
-
const itemData: any = items[i];
|
|
369
|
+
const itemData: Record<string, unknown> = items[i] as Record<string, unknown>;
|
|
368
370
|
|
|
369
371
|
// Create iteration context with item and optionally index
|
|
370
372
|
const context: Record<string, unknown> = {
|
|
371
373
|
...data,
|
|
372
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
373
374
|
[loop.itemVar]: itemData,
|
|
374
|
-
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
|
375
375
|
};
|
|
376
376
|
|
|
377
377
|
if (loop.indexVar) {
|
|
378
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
379
378
|
context[loop.indexVar] = i;
|
|
380
|
-
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
|
|
381
379
|
}
|
|
382
380
|
|
|
383
381
|
// Render each element in the loop
|
package/src/utils/image.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Supports multiple dithering algorithms and image preprocessing.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { BluetoothPrintError, ErrorCode } from '@/errors/baseError';
|
|
9
|
+
|
|
8
10
|
export class ImageProcessing {
|
|
9
11
|
// Pre-computed Bayer matrices for ordered dithering
|
|
10
12
|
private static readonly BAYER_MATRIX_2: ReadonlyArray<ReadonlyArray<number>> = [
|
|
@@ -84,7 +86,8 @@ export class ImageProcessing {
|
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
if (data.length !== width * height * 4) {
|
|
87
|
-
throw new
|
|
89
|
+
throw new BluetoothPrintError(
|
|
90
|
+
ErrorCode.INVALID_IMAGE_DATA,
|
|
88
91
|
`Invalid image data length: expected ${width * height * 4}, got ${data.length}`
|
|
89
92
|
);
|
|
90
93
|
}
|
|
@@ -151,10 +151,13 @@ export async function batchProcess<T, R>(
|
|
|
151
151
|
const batchResults = await processor(batch, batchIndex);
|
|
152
152
|
results.push(...batchResults);
|
|
153
153
|
} catch (error) {
|
|
154
|
-
// Log the error and
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
// Log the error and re-throw so the caller can handle the failure
|
|
155
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
156
|
+
console.error(
|
|
157
|
+
`[batchProcess] Error processing batch ${batchIndex}/${totalBatches}:`,
|
|
158
|
+
normalizedError.message
|
|
159
|
+
);
|
|
160
|
+
throw normalizedError;
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
// 添加小延迟,避免阻塞
|