jc-printer-sdk-ts 0.1.0 → 0.1.1

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,648 @@
1
+ import { JCPrintApi } from "./client.js";
2
+ import { createConnectFrame, createFrame, createRowFrames, fallbackProfile, identifyProfile, parseFrame, parseModelId, parseProtocolVersion, rasterizeDocumentPage, } from "./niimbotProtocol.js";
3
+ export const NIIMBOT_WEB_BLUETOOTH_SERVICE_UUID = "e7810a71-73ae-499d-8c15-faa9aef0c3f2";
4
+ export const NIIMBOT_WEB_BLUETOOTH_CHARACTERISTIC_UUID = "bef8d6c9-9c21-4c9e-b632-bd58c1009f9f";
5
+ export const DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS = {
6
+ serviceUuid: NIIMBOT_WEB_BLUETOOTH_SERVICE_UUID,
7
+ characteristicUuid: NIIMBOT_WEB_BLUETOOTH_CHARACTERISTIC_UUID,
8
+ deviceNameFilter: "",
9
+ scanMode: "all",
10
+ optionalServices: [],
11
+ chunkSize: 180,
12
+ };
13
+ function clone(value) {
14
+ if (typeof globalThis.structuredClone === "function") {
15
+ return globalThis.structuredClone(value);
16
+ }
17
+ return JSON.parse(JSON.stringify(value));
18
+ }
19
+ function normalizeUuid(uuid) {
20
+ return uuid.trim().toLowerCase();
21
+ }
22
+ function uniqueStrings(values) {
23
+ return Array.from(new Set(values.map((value) => value?.trim()).filter((value) => !!value)));
24
+ }
25
+ function resolveOptions(options = {}) {
26
+ const chunkSize = Number.isFinite(options.chunkSize)
27
+ ? Math.trunc(Number(options.chunkSize))
28
+ : DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.chunkSize;
29
+ return {
30
+ serviceUuid: options.serviceUuid ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.serviceUuid,
31
+ characteristicUuid: options.characteristicUuid ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.characteristicUuid,
32
+ deviceNameFilter: options.deviceNameFilter?.trim() ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.deviceNameFilter,
33
+ scanMode: options.scanMode ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.scanMode,
34
+ optionalServices: [...(options.optionalServices ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.optionalServices)],
35
+ chunkSize: Math.max(20, chunkSize),
36
+ };
37
+ }
38
+ function defaultPrinterInfo() {
39
+ return {
40
+ deviceType: 2,
41
+ deviceName: "",
42
+ deviceVersion: "",
43
+ softwareVersion: "web-bluetooth",
44
+ deviceAddress: "",
45
+ deviceAddrType: 2,
46
+ deviceDPI: 0,
47
+ deviceWidth: 0,
48
+ manufacturer: "",
49
+ seriesName: "",
50
+ devIntName: "",
51
+ peripheralFlags: 0,
52
+ hardwareFlags: 0,
53
+ softwareFlags: 0,
54
+ };
55
+ }
56
+ function defaultPaperInfo() {
57
+ return {
58
+ state: 0,
59
+ gapHeightPixel: 0,
60
+ totalHeightPixel: 0,
61
+ paperType: 0,
62
+ gapHeight: 0,
63
+ totalHeight: 0,
64
+ paperWidthPixel: 0,
65
+ paperWidth: 0,
66
+ direction: 0,
67
+ tailLengthPixel: 0,
68
+ tailLength: 0,
69
+ };
70
+ }
71
+ export class WebBluetoothPrinterTransport {
72
+ constructor(options = {}) {
73
+ this.printerName = "";
74
+ this.printerInfo = defaultPrinterInfo();
75
+ this.paperInfo = defaultPaperInfo();
76
+ this.printerState = "Disconnected";
77
+ this.lastAddress = "";
78
+ this.reconnecting = false;
79
+ this.connectedProfile = null;
80
+ this.b1HandshakeDone = false;
81
+ this.responseQueue = [];
82
+ this.handleDisconnect = () => {
83
+ this.printerState = "Disconnected";
84
+ this.server = undefined;
85
+ this.characteristic = undefined;
86
+ this.sdkCallback?.onDisConnect();
87
+ };
88
+ this.handleCharacteristicChanged = (event) => {
89
+ const characteristic = event.target;
90
+ const value = characteristic?.value;
91
+ if (!value) {
92
+ return;
93
+ }
94
+ const frame = parseFrame(value);
95
+ if (!frame) {
96
+ return;
97
+ }
98
+ const response = { cmd: frame.cmd, data: frame.data };
99
+ const pending = this.pendingResponse;
100
+ if (pending && pending.expected.has(response.cmd)) {
101
+ clearTimeout(pending.timeoutId);
102
+ this.pendingResponse = undefined;
103
+ pending.resolve(response);
104
+ return;
105
+ }
106
+ this.responseQueue.push(response);
107
+ };
108
+ this.options = resolveOptions(options);
109
+ }
110
+ configure(options) {
111
+ this.options = resolveOptions({
112
+ ...this.options,
113
+ ...options,
114
+ });
115
+ }
116
+ getLastPayload() {
117
+ return this.lastPayload ? clone(this.lastPayload) : undefined;
118
+ }
119
+ setSdkCallback(callback) {
120
+ this.sdkCallback = callback;
121
+ if (callback && this.printerState !== "Disconnected" && this.lastAddress) {
122
+ callback.onConnectSuccess(this.lastAddress, 20);
123
+ }
124
+ }
125
+ async initSdk() {
126
+ return true;
127
+ }
128
+ async init() {
129
+ return true;
130
+ }
131
+ async connectBluetoothPrinter(address) {
132
+ if (!navigator.bluetooth) {
133
+ throw new Error("Web Bluetooth is not available in this browser.");
134
+ }
135
+ const deviceFilter = this.options.deviceNameFilter?.trim() || address.trim();
136
+ const serviceUuid = normalizeUuid(this.options.serviceUuid);
137
+ const characteristicUuid = normalizeUuid(this.options.characteristicUuid);
138
+ if (!serviceUuid || !characteristicUuid) {
139
+ throw new Error("Service UUID and characteristic UUID are required for Web Bluetooth printing.");
140
+ }
141
+ const optionalServices = uniqueStrings([serviceUuid, ...(this.options.optionalServices ?? [])]);
142
+ const useNameFilter = this.options.scanMode === "name" && deviceFilter;
143
+ const requestOptions = useNameFilter
144
+ ? {
145
+ filters: [{ namePrefix: deviceFilter }],
146
+ optionalServices,
147
+ }
148
+ : {
149
+ acceptAllDevices: true,
150
+ optionalServices,
151
+ };
152
+ const device = await navigator.bluetooth.requestDevice(requestOptions);
153
+ const server = await device.gatt?.connect();
154
+ if (!server) {
155
+ throw new Error("Failed to open the selected Bluetooth device.");
156
+ }
157
+ const service = await server.getPrimaryService(serviceUuid);
158
+ const characteristic = await service.getCharacteristic(characteristicUuid);
159
+ this.device = device;
160
+ this.server = server;
161
+ this.characteristic = characteristic;
162
+ this.lastAddress = device.name || device.id || deviceFilter || serviceUuid;
163
+ this.printerName = this.lastAddress;
164
+ this.printerState = "Connected";
165
+ this.printerInfo = {
166
+ ...defaultPrinterInfo(),
167
+ deviceName: this.printerName,
168
+ deviceAddress: this.lastAddress,
169
+ softwareVersion: "web-bluetooth",
170
+ deviceAddrType: 2,
171
+ };
172
+ this.paperInfo = defaultPaperInfo();
173
+ device.addEventListener("gattserverdisconnected", this.handleDisconnect);
174
+ characteristic.addEventListener("characteristicvaluechanged", this.handleCharacteristicChanged);
175
+ await characteristic.startNotifications();
176
+ await this.handshakeAndIdentify();
177
+ this.sdkCallback?.onConnectSuccess(this.lastAddress, 20);
178
+ return 0;
179
+ }
180
+ async openPrinterByAddress(address) {
181
+ return this.connectBluetoothPrinter(address);
182
+ }
183
+ async openPrinterSync(address) {
184
+ return this.connectBluetoothPrinter(address);
185
+ }
186
+ async connectWifiPrinter() {
187
+ throw new Error("Wi-Fi printing is not implemented in the browser transport.");
188
+ }
189
+ async configurationWifi() {
190
+ return 0;
191
+ }
192
+ async getConfigurationWifiName() {
193
+ return "";
194
+ }
195
+ async scanWifiPrinter(callback) {
196
+ callback.onFinish();
197
+ }
198
+ async setPrinterAutoShutdownTime() {
199
+ return 0;
200
+ }
201
+ async setTotalPrintQuantity() {
202
+ return;
203
+ }
204
+ async setTotalQuantityOfPrints() {
205
+ return;
206
+ }
207
+ async startPrintJob(density, paperType, printMode, callback) {
208
+ this.currentJob = { density, paperType, printMode, callback };
209
+ this.printerState = "Printing";
210
+ callback.onProgress(0, 0, {
211
+ phase: "start",
212
+ density,
213
+ paperType,
214
+ printMode,
215
+ });
216
+ }
217
+ async commitData(jsonPages, infoPages) {
218
+ await this.writePayload({
219
+ kind: "jc-printer-sdk-ts/web-bluetooth",
220
+ sentAt: new Date().toISOString(),
221
+ job: {
222
+ density: this.currentJob?.density ?? 0,
223
+ paperType: this.currentJob?.paperType ?? 0,
224
+ printMode: this.currentJob?.printMode ?? 0,
225
+ },
226
+ jsonPages: clone(jsonPages),
227
+ infoPages: clone(infoPages),
228
+ });
229
+ }
230
+ async commitDocument(document, infoPages) {
231
+ const profile = await this.resolveProfile(document);
232
+ this.lastPayload = {
233
+ kind: "jc-printer-sdk-ts/web-bluetooth",
234
+ sentAt: new Date().toISOString(),
235
+ job: {
236
+ density: document.job.density ?? this.currentJob?.density ?? 0,
237
+ paperType: document.job.paperType ?? this.currentJob?.paperType ?? 0,
238
+ printMode: document.job.printMode ?? this.currentJob?.printMode ?? 0,
239
+ },
240
+ document: clone(document),
241
+ infoPages: clone(infoPages),
242
+ profile: { ...profile },
243
+ };
244
+ await this.printNiimbotDocument(document, profile);
245
+ this.currentJob?.callback?.onProgress(document.pages.length, document.job.totalQuantity ?? 1, {
246
+ pages: document.pages.length,
247
+ profile: profile.label,
248
+ task: profile.task,
249
+ dpi: profile.dpi,
250
+ });
251
+ this.currentJob?.callback?.onBufferFree(document.pages.length, profile.printheadWidthPx);
252
+ }
253
+ async endJob() {
254
+ return this.endPrintJob();
255
+ }
256
+ async endPrintJob() {
257
+ this.printerState = this.server?.connected ? "Connected" : "Disconnected";
258
+ this.currentJob = undefined;
259
+ return true;
260
+ }
261
+ async cancelJob() {
262
+ this.currentJob?.callback?.onCancelJob(true);
263
+ this.currentJob = undefined;
264
+ this.printerState = this.server?.connected ? "Connected" : "Disconnected";
265
+ return true;
266
+ }
267
+ async cancel() {
268
+ await this.cancelJob();
269
+ }
270
+ async reopenPrinter() {
271
+ if (this.reconnecting || !this.device?.gatt || this.server?.connected) {
272
+ return !!this.server?.connected;
273
+ }
274
+ this.reconnecting = true;
275
+ try {
276
+ const server = await this.device.gatt.connect();
277
+ this.server = server;
278
+ const service = await server.getPrimaryService(normalizeUuid(this.options.serviceUuid));
279
+ const characteristic = await service.getCharacteristic(normalizeUuid(this.options.characteristicUuid));
280
+ this.characteristic = characteristic;
281
+ characteristic.addEventListener("characteristicvaluechanged", this.handleCharacteristicChanged);
282
+ await characteristic.startNotifications();
283
+ await this.handshakeAndIdentify();
284
+ this.printerState = "Connected";
285
+ this.sdkCallback?.onConnectSuccess(this.lastAddress, 20);
286
+ return true;
287
+ }
288
+ finally {
289
+ this.reconnecting = false;
290
+ }
291
+ }
292
+ async reopenPrinterSync() {
293
+ return this.reopenPrinter();
294
+ }
295
+ async close() {
296
+ this.currentJob?.callback?.onCancelJob(true);
297
+ this.currentJob = undefined;
298
+ this.printerState = "Disconnected";
299
+ if (this.device) {
300
+ this.device.removeEventListener("gattserverdisconnected", this.handleDisconnect);
301
+ }
302
+ if (this.characteristic) {
303
+ this.characteristic.removeEventListener("characteristicvaluechanged", this.handleCharacteristicChanged);
304
+ try {
305
+ await this.characteristic.stopNotifications();
306
+ }
307
+ catch {
308
+ // Some browsers reject when the device is already gone.
309
+ }
310
+ }
311
+ if (this.server?.connected) {
312
+ this.server.disconnect();
313
+ }
314
+ this.server = undefined;
315
+ this.characteristic = undefined;
316
+ this.printerName = "";
317
+ this.lastAddress = "";
318
+ this.sdkCallback?.onDisConnect();
319
+ }
320
+ async quit() {
321
+ await this.close();
322
+ }
323
+ async printBitmap(bitmap, _bundle = {}) {
324
+ await this.writeJson({
325
+ kind: "jc-printer-sdk-ts/web-bluetooth-bitmap",
326
+ sentAt: new Date().toISOString(),
327
+ bitmap,
328
+ });
329
+ return true;
330
+ }
331
+ async printATBitmap(bitmap, _bundle = {}) {
332
+ await this.writeJson({
333
+ kind: "jc-printer-sdk-ts/web-bluetooth-at-bitmap",
334
+ sentAt: new Date().toISOString(),
335
+ bitmap,
336
+ });
337
+ return true;
338
+ }
339
+ async getPaperInfo() {
340
+ return clone(this.paperInfo);
341
+ }
342
+ async isConnection() {
343
+ return this.server?.connected ? 0 : -1;
344
+ }
345
+ async getPrintScaleMultiplier() {
346
+ return 1;
347
+ }
348
+ async getPrinterName() {
349
+ return this.printerName;
350
+ }
351
+ async getPrinterInfo() {
352
+ return clone(this.printerInfo);
353
+ }
354
+ async getPrinterState() {
355
+ return this.printerState;
356
+ }
357
+ async waitPrinterState(state, timeoutMs) {
358
+ const startedAt = Date.now();
359
+ while (Date.now() - startedAt < timeoutMs) {
360
+ if (this.printerState === state) {
361
+ return true;
362
+ }
363
+ await new Promise((resolve) => setTimeout(resolve, 100));
364
+ }
365
+ return false;
366
+ }
367
+ async measureFontHeight(text, _x, _y, _width, _height, _rotation, fontSize) {
368
+ const lines = text.split(/\r?\n/).length;
369
+ return Math.max(fontSize, Math.ceil(fontSize * 1.2 * lines));
370
+ }
371
+ async generatePreviewImage(json, info) {
372
+ return {
373
+ width: 0,
374
+ height: 0,
375
+ description: "web-bluetooth-preview",
376
+ meta: { json, info },
377
+ };
378
+ }
379
+ async generatePrintPreviewImage(json, widthMm, heightMm, orientation) {
380
+ return {
381
+ width: widthMm,
382
+ height: heightMm,
383
+ description: "web-bluetooth-print-preview",
384
+ meta: { json, orientation },
385
+ };
386
+ }
387
+ setPrinterState(state) {
388
+ this.printerState = state;
389
+ }
390
+ setPrinterInfo(info) {
391
+ this.printerInfo = clone(info);
392
+ }
393
+ setPaperInfo(info) {
394
+ this.paperInfo = clone(info);
395
+ }
396
+ setScanResults(_results) {
397
+ return;
398
+ }
399
+ emitPrintProgress(pageIndex, quantityIndex, meta) {
400
+ this.currentJob?.callback?.onProgress(pageIndex, quantityIndex, meta ?? {});
401
+ }
402
+ emitPrintError(errorCode, printState) {
403
+ this.currentJob?.callback?.onError(errorCode, printState);
404
+ }
405
+ emitBufferFree(pageIndex, bufferSize) {
406
+ this.currentJob?.callback?.onBufferFree(pageIndex, bufferSize);
407
+ }
408
+ emitCancel(success) {
409
+ this.currentJob?.callback?.onCancelJob(success);
410
+ }
411
+ async writePayload(payload) {
412
+ this.lastPayload = clone(payload);
413
+ await this.writeJson(payload);
414
+ this.currentJob?.callback?.onProgress(1, 1, {
415
+ bytes: JSON.stringify(payload).length,
416
+ });
417
+ this.currentJob?.callback?.onBufferFree(1, JSON.stringify(payload).length);
418
+ }
419
+ async writeJson(payload) {
420
+ const characteristic = this.characteristic;
421
+ if (!characteristic) {
422
+ throw new Error("No writable Bluetooth characteristic is connected.");
423
+ }
424
+ const text = JSON.stringify(payload);
425
+ const encoded = new TextEncoder().encode(text);
426
+ const chunkSize = Math.max(20, this.options.chunkSize ?? 180);
427
+ const canWriteWithoutResponse = typeof characteristic.writeValueWithoutResponse === "function";
428
+ for (let offset = 0; offset < encoded.length; offset += chunkSize) {
429
+ const chunk = encoded.slice(offset, offset + chunkSize);
430
+ await this.writeBytes(chunk, 0, characteristic, canWriteWithoutResponse);
431
+ }
432
+ }
433
+ async handshakeAndIdentify() {
434
+ this.responseQueue = [];
435
+ this.connectedProfile = null;
436
+ this.b1HandshakeDone = false;
437
+ await this.writeBytes(createConnectFrame());
438
+ await this.waitForCommand([0xc2], 1500, true);
439
+ const status = await this.sendCommand(0xa5, [0x01], [0xb5], 1500, true);
440
+ const protocolVersion = status ? parseProtocolVersion(status.data) : undefined;
441
+ const model = await this.sendCommand(0x40, [0x08], [0x48], 1500, true);
442
+ const modelId = model ? parseModelId(model.data) : undefined;
443
+ this.connectedProfile = identifyProfile(modelId, protocolVersion);
444
+ if (this.connectedProfile?.task === "b1") {
445
+ await this.finishB1Handshake();
446
+ }
447
+ const profile = this.connectedProfile;
448
+ if (profile) {
449
+ this.printerInfo = {
450
+ ...this.printerInfo,
451
+ deviceType: profile.modelId ?? 0,
452
+ deviceDPI: profile.dpi,
453
+ deviceWidth: profile.printheadWidthPx,
454
+ seriesName: profile.label,
455
+ hardwareFlags: profile.modelId ?? 0,
456
+ softwareFlags: profile.protocolVersion ?? 0,
457
+ };
458
+ }
459
+ }
460
+ async finishB1Handshake() {
461
+ if (this.b1HandshakeDone) {
462
+ return;
463
+ }
464
+ for (const subCode of [0x0b, 0x0d, 0x0a, 0x07, 0x03, 0x0c, 0x09]) {
465
+ await this.sendCommand(0x40, [subCode], [0x40 + subCode], 1500, true);
466
+ }
467
+ await this.sendCommand(0xdc, [0x04], [0xd9], 1500, true);
468
+ this.b1HandshakeDone = true;
469
+ }
470
+ async resolveProfile(document) {
471
+ const requestedTask = document.job.meta?.printTask;
472
+ const task = requestedTask === "b1" || requestedTask === "v4" ? requestedTask : "auto";
473
+ const requestedDpi = Number(document.job.meta?.dpi ?? 0);
474
+ if (task === "auto" && this.connectedProfile) {
475
+ return clone(this.connectedProfile);
476
+ }
477
+ const fallback = fallbackProfile(task, requestedDpi);
478
+ if (fallback.task === "b1") {
479
+ await this.finishB1Handshake();
480
+ }
481
+ return fallback;
482
+ }
483
+ async printNiimbotDocument(document, profile) {
484
+ if (!document.pages.length) {
485
+ throw new Error("The print document has no pages.");
486
+ }
487
+ const copies = Math.max(1, Math.trunc(Number(document.job.totalQuantity ?? 1)));
488
+ const physicalPages = document.pages.length * copies;
489
+ const density = this.normalizeDensity(Number(document.job.density ?? 3), profile.task);
490
+ const speed = this.normalizeSpeed(Number(document.job.meta?.speed ?? 2));
491
+ this.currentJob?.callback?.onProgress(0, 0, {
492
+ phase: "protocol-start",
493
+ task: profile.task,
494
+ profile: profile.label,
495
+ dpi: profile.dpi,
496
+ physicalPages,
497
+ });
498
+ await this.sendCommand(0x21, [density], [0x31], 2000);
499
+ await this.sendCommand(0x23, [Number(document.job.paperType ?? 1) & 0xff], [0x33], 2000);
500
+ const startPayload = profile.task === "v4"
501
+ ? [physicalPages >> 8, physicalPages & 0xff, 0, 0, 0, 0, 0, speed, 0]
502
+ : [physicalPages >> 8, physicalPages & 0xff, 0, 0, 0, 0, 0];
503
+ await this.sendCommand(0x01, startPayload, [0x02], 3000);
504
+ if (profile.task === "v4") {
505
+ await this.sendCommand(0xa3, [0x01], undefined, 0, true);
506
+ await delay(30);
507
+ }
508
+ let printedPage = 0;
509
+ for (let copyIndex = 0; copyIndex < copies; copyIndex += 1) {
510
+ for (let pageIndex = 0; pageIndex < document.pages.length; pageIndex += 1) {
511
+ const page = document.pages[pageIndex];
512
+ const raster = await rasterizeDocumentPage(page, document, profile);
513
+ if (profile.task === "b1") {
514
+ await this.sendCommand(0x03, [0x01], [0x04], 3000);
515
+ }
516
+ else {
517
+ await this.sendCommand(0xa3, [0x01], undefined, 0, true);
518
+ await delay(30);
519
+ }
520
+ const sizePayload = profile.task === "v4"
521
+ ? [
522
+ raster.heightPx >> 8,
523
+ raster.heightPx & 0xff,
524
+ raster.widthPx >> 8,
525
+ raster.widthPx & 0xff,
526
+ 0x00,
527
+ 0x01,
528
+ 0x00,
529
+ 0x00,
530
+ 0x00,
531
+ 0x00,
532
+ 0x00,
533
+ 0x00,
534
+ 0x00,
535
+ ]
536
+ : [
537
+ raster.heightPx >> 8,
538
+ raster.heightPx & 0xff,
539
+ raster.widthPx >> 8,
540
+ raster.widthPx & 0xff,
541
+ 0x00,
542
+ 0x01,
543
+ ];
544
+ await this.sendCommand(0x13, sizePayload, [0x14], 3000);
545
+ await this.writeRows(createRowFrames(raster), profile.paceMs);
546
+ await this.sendCommand(0xe3, [0x01], [0xe4], 8000);
547
+ printedPage += 1;
548
+ this.currentJob?.callback?.onProgress(pageIndex + 1, copyIndex + 1, {
549
+ phase: "page-sent",
550
+ page: printedPage,
551
+ total: physicalPages,
552
+ widthPx: raster.widthPx,
553
+ heightPx: raster.heightPx,
554
+ stride: raster.stride,
555
+ });
556
+ }
557
+ }
558
+ await this.waitUntilPrinted(physicalPages);
559
+ await this.sendCommand(0xf3, [0x01], [0xf4], 8000);
560
+ if (profile.task === "v4") {
561
+ await this.sendCommand(0xdc, [0x01], undefined, 0, true);
562
+ }
563
+ }
564
+ async waitUntilPrinted(pageNumber) {
565
+ const startedAt = Date.now();
566
+ this.responseQueue = this.responseQueue.filter((response) => response.cmd !== 0xb3);
567
+ while (Date.now() - startedAt < 25000) {
568
+ const response = await this.sendCommand(0xa3, [0x01], [0xb3], 1200, true);
569
+ if (response?.data && response.data.length >= 2) {
570
+ const page = (response.data[0] << 8) | response.data[1];
571
+ if (page >= pageNumber) {
572
+ return;
573
+ }
574
+ }
575
+ await delay(400);
576
+ }
577
+ }
578
+ async writeRows(frames, paceMs) {
579
+ for (const frame of frames) {
580
+ await this.writeBytes(frame, paceMs);
581
+ }
582
+ }
583
+ async sendCommand(cmd, data, expected, timeoutMs = 2000, optional = false) {
584
+ const wait = expected?.length ? this.waitForCommand(expected, timeoutMs, optional) : Promise.resolve(null);
585
+ await this.writeBytes(createFrame(cmd, data));
586
+ return wait;
587
+ }
588
+ async waitForCommand(expected, timeoutMs, optional) {
589
+ const expectedSet = new Set(expected.map((cmd) => cmd & 0xff));
590
+ const queuedIndex = this.responseQueue.findIndex((response) => expectedSet.has(response.cmd));
591
+ if (queuedIndex >= 0) {
592
+ const [queued] = this.responseQueue.splice(queuedIndex, 1);
593
+ return queued;
594
+ }
595
+ return new Promise((resolve, reject) => {
596
+ this.pendingResponse = {
597
+ expected: expectedSet,
598
+ resolve,
599
+ reject,
600
+ optional,
601
+ timeoutId: window.setTimeout(() => {
602
+ this.pendingResponse = undefined;
603
+ if (optional) {
604
+ resolve(null);
605
+ }
606
+ else {
607
+ reject(new Error(`Timed out waiting for printer response 0x${expected[0].toString(16)}.`));
608
+ }
609
+ }, timeoutMs),
610
+ };
611
+ });
612
+ }
613
+ async writeBytes(bytes, paceMs = 0, characteristic = this.characteristic, canWriteWithoutResponse = !!characteristic && typeof characteristic.writeValueWithoutResponse === "function") {
614
+ if (!characteristic) {
615
+ throw new Error("No writable Bluetooth characteristic is connected.");
616
+ }
617
+ const payload = new ArrayBuffer(bytes.byteLength);
618
+ new Uint8Array(payload).set(bytes);
619
+ if (canWriteWithoutResponse) {
620
+ await characteristic.writeValueWithoutResponse(payload);
621
+ }
622
+ else {
623
+ await characteristic.writeValue(payload);
624
+ }
625
+ if (paceMs > 0) {
626
+ await delay(paceMs);
627
+ }
628
+ }
629
+ normalizeDensity(value, task) {
630
+ if (task === "b1") {
631
+ return Math.max(1, Math.min(5, Math.round(value)));
632
+ }
633
+ return Math.max(1, Math.min(14, Math.round(value)));
634
+ }
635
+ normalizeSpeed(value) {
636
+ return Math.max(0, Math.min(4, Math.round(value)));
637
+ }
638
+ }
639
+ function delay(ms) {
640
+ return new Promise((resolve) => window.setTimeout(resolve, ms));
641
+ }
642
+ export function createWebBluetoothPrinterTransport(options = {}) {
643
+ return new WebBluetoothPrinterTransport(options);
644
+ }
645
+ export function createWebBluetoothPrinterClient(options = {}, sdkCallback) {
646
+ return new JCPrintApi(createWebBluetoothPrinterTransport(options), sdkCallback);
647
+ }
648
+ //# sourceMappingURL=webBluetoothTransport.js.map