@nitra/zebra 6.1.8 → 6.2.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/dist/plugin.js +871 -38
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -3,8 +3,8 @@ import { WebPlugin, registerPlugin } from '@capacitor/core';
|
|
|
3
3
|
/**
|
|
4
4
|
* ZebraPrinterWeb - Web implementation for development
|
|
5
5
|
*
|
|
6
|
-
* Uses local printer-server.js (TCP/IP)
|
|
7
|
-
*
|
|
6
|
+
* Uses local printer-server.js (TCP/IP) or Web Bluetooth API.
|
|
7
|
+
* Falls back to BLE when TCP/IP is not available (like iOS).
|
|
8
8
|
*
|
|
9
9
|
* Production (iOS) uses the native BLE implementation.
|
|
10
10
|
*/
|
|
@@ -24,12 +24,103 @@ import { WebPlugin, registerPlugin } from '@capacitor/core';
|
|
|
24
24
|
|
|
25
25
|
const API_BASE = '/api/printer';
|
|
26
26
|
|
|
27
|
+
// Zebra printer BLE service UUIDs
|
|
28
|
+
const ZEBRA_SERVICE_UUID = '38eb4a80-c570-11e3-9507-0002a5d5c51b';
|
|
29
|
+
const ZEBRA_WRITE_CHAR_UUID = '38eb4a82-c570-11e3-9507-0002a5d5c51b';
|
|
30
|
+
// Alternative service for some Zebra models
|
|
31
|
+
const ZEBRA_SPP_SERVICE_UUID = '00001101-0000-1000-8000-00805f9b34fb';
|
|
32
|
+
|
|
33
|
+
// LocalStorage key for persisting printer settings
|
|
34
|
+
const STORAGE_KEY = 'zebra_printer_settings';
|
|
35
|
+
|
|
27
36
|
class ZebraPrinterWeb extends WebPlugin {
|
|
28
37
|
constructor() {
|
|
29
38
|
super();
|
|
30
|
-
this.connectedPrinter = null;
|
|
31
39
|
this.serviceAvailable = null;
|
|
32
40
|
this.defaultSubnet = this._getDefaultSubnet();
|
|
41
|
+
// BLE state
|
|
42
|
+
this.bleDevice = null;
|
|
43
|
+
this.bleServer = null;
|
|
44
|
+
this.bleCharacteristic = null;
|
|
45
|
+
this.discoveredBleDevices = [];
|
|
46
|
+
// Load last printer from localStorage
|
|
47
|
+
this.connectedPrinter = this._loadFromStorage();
|
|
48
|
+
if (this.connectedPrinter) {
|
|
49
|
+
console.log(
|
|
50
|
+
'[Storage] Loaded:',
|
|
51
|
+
this.connectedPrinter.name || this.connectedPrinter.ip
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
57
|
+
// LOCAL STORAGE PERSISTENCE
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load printer settings from localStorage
|
|
62
|
+
* @returns {Object|null} Saved printer settings or null
|
|
63
|
+
*/
|
|
64
|
+
_loadFromStorage() {
|
|
65
|
+
try {
|
|
66
|
+
if (typeof localStorage === 'undefined') return null;
|
|
67
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
68
|
+
return saved ? JSON.parse(saved) : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Save printer settings to localStorage
|
|
76
|
+
* @param {Object} printer - Printer settings to save
|
|
77
|
+
*/
|
|
78
|
+
_saveToStorage(printer) {
|
|
79
|
+
try {
|
|
80
|
+
if (typeof localStorage === 'undefined') return;
|
|
81
|
+
if (printer) {
|
|
82
|
+
// Don't save BLE device reference (can't be serialized)
|
|
83
|
+
const toSave = { ...printer };
|
|
84
|
+
delete toSave.bleDevice;
|
|
85
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
|
|
86
|
+
console.log('[Storage] Saved:', toSave.name || toSave.ip);
|
|
87
|
+
} else {
|
|
88
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
89
|
+
console.log('[Storage] Cleared');
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Ignore storage errors
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Clear saved printer from localStorage
|
|
98
|
+
* @returns {{success: boolean, message: string}} Result object
|
|
99
|
+
*/
|
|
100
|
+
clearSavedPrinter() {
|
|
101
|
+
this._saveToStorage(null);
|
|
102
|
+
return { success: true, message: 'Saved printer cleared' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if Web Bluetooth is supported
|
|
107
|
+
* @returns {boolean} True if Web Bluetooth API is available
|
|
108
|
+
*/
|
|
109
|
+
_isBleSupported() {
|
|
110
|
+
return (
|
|
111
|
+
typeof navigator !== 'undefined' && navigator.bluetooth !== undefined
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Delay helper for BLE chunk sending
|
|
117
|
+
* @param {number} ms - Milliseconds to delay
|
|
118
|
+
* @returns {Promise<void>} Resolves after delay
|
|
119
|
+
*/
|
|
120
|
+
_delay(ms) {
|
|
121
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
122
|
+
setTimeout(resolve, ms);
|
|
123
|
+
return promise;
|
|
33
124
|
}
|
|
34
125
|
|
|
35
126
|
/**
|
|
@@ -56,6 +147,260 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
56
147
|
}
|
|
57
148
|
}
|
|
58
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Scan for BLE printers using Web Bluetooth API
|
|
152
|
+
* @returns {Promise<ScanResult>} List of discovered BLE printers
|
|
153
|
+
*/
|
|
154
|
+
async _scanBlePrinters() {
|
|
155
|
+
if (!this._isBleSupported()) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error: 'Web Bluetooth is not supported in this browser',
|
|
159
|
+
printers: [],
|
|
160
|
+
count: 0,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
console.log('[BLE] Scanning...');
|
|
166
|
+
|
|
167
|
+
// Request device with Zebra name prefixes
|
|
168
|
+
const device = await navigator.bluetooth.requestDevice({
|
|
169
|
+
filters: [
|
|
170
|
+
{ namePrefix: 'Zebra' },
|
|
171
|
+
{ namePrefix: 'ZT' },
|
|
172
|
+
{ namePrefix: 'ZD' },
|
|
173
|
+
{ namePrefix: 'ZQ' },
|
|
174
|
+
{ namePrefix: 'XXZHN' },
|
|
175
|
+
],
|
|
176
|
+
optionalServices: [ZEBRA_SERVICE_UUID, ZEBRA_SPP_SERVICE_UUID],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
console.log('[BLE] Found:', device.name);
|
|
180
|
+
|
|
181
|
+
// Store discovered device
|
|
182
|
+
if (!this.discoveredBleDevices.some((d) => d.id === device.id)) {
|
|
183
|
+
this.discoveredBleDevices.push(device);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
success: true,
|
|
188
|
+
printers: this.discoveredBleDevices.map((d) => ({
|
|
189
|
+
name: d.name || 'Zebra Printer',
|
|
190
|
+
address: d.id,
|
|
191
|
+
type: 'bluetooth',
|
|
192
|
+
paired: false,
|
|
193
|
+
})),
|
|
194
|
+
count: this.discoveredBleDevices.length,
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.warn('[BLE] Scan failed:', error.message);
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
error: error.message || 'BLE scan failed',
|
|
201
|
+
printers: [],
|
|
202
|
+
count: 0,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Connect to a BLE printer
|
|
209
|
+
* @param {string} deviceId - Device ID from scan
|
|
210
|
+
* @returns {Promise<ConnectResult>} Connection result with success status
|
|
211
|
+
*/
|
|
212
|
+
async _connectBle(deviceId) {
|
|
213
|
+
if (!this._isBleSupported()) {
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
connected: false,
|
|
217
|
+
error: 'Web Bluetooth is not supported',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
let device = this.discoveredBleDevices.find((d) => d.id === deviceId);
|
|
223
|
+
|
|
224
|
+
// If device not found, request new device
|
|
225
|
+
if (!device) {
|
|
226
|
+
console.log('[BLE] Device not cached, requesting...');
|
|
227
|
+
device = await navigator.bluetooth.requestDevice({
|
|
228
|
+
filters: [
|
|
229
|
+
{ namePrefix: 'Zebra' },
|
|
230
|
+
{ namePrefix: 'ZT' },
|
|
231
|
+
{ namePrefix: 'ZD' },
|
|
232
|
+
{ namePrefix: 'ZQ' },
|
|
233
|
+
{ namePrefix: 'XXZHN' },
|
|
234
|
+
],
|
|
235
|
+
optionalServices: [ZEBRA_SERVICE_UUID, ZEBRA_SPP_SERVICE_UUID],
|
|
236
|
+
});
|
|
237
|
+
this.discoveredBleDevices.push(device);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log('[BLE] Connecting to', device.name);
|
|
241
|
+
|
|
242
|
+
// Connect to GATT server
|
|
243
|
+
this.bleServer = await device.gatt.connect();
|
|
244
|
+
this.bleDevice = device;
|
|
245
|
+
|
|
246
|
+
console.log('[BLE] GATT connected');
|
|
247
|
+
|
|
248
|
+
// Try to get Zebra service
|
|
249
|
+
let service;
|
|
250
|
+
try {
|
|
251
|
+
service = await this.bleServer.getPrimaryService(ZEBRA_SERVICE_UUID);
|
|
252
|
+
console.log('[BLE] Found Zebra service');
|
|
253
|
+
} catch {
|
|
254
|
+
// Try alternative service
|
|
255
|
+
console.log('[BLE] Trying alternative services...');
|
|
256
|
+
const services = await this.bleServer.getPrimaryServices();
|
|
257
|
+
console.log('[BLE] Found', services.length, 'services');
|
|
258
|
+
|
|
259
|
+
for (const svc of services) {
|
|
260
|
+
console.log(` - Service: ${svc.uuid}`);
|
|
261
|
+
try {
|
|
262
|
+
const chars = await svc.getCharacteristics();
|
|
263
|
+
for (const char of chars) {
|
|
264
|
+
console.log(
|
|
265
|
+
` - Characteristic: ${char.uuid}, props:`,
|
|
266
|
+
char.properties
|
|
267
|
+
);
|
|
268
|
+
if (
|
|
269
|
+
char.properties.write ||
|
|
270
|
+
char.properties.writeWithoutResponse
|
|
271
|
+
) {
|
|
272
|
+
service = svc;
|
|
273
|
+
this.bleCharacteristic = char;
|
|
274
|
+
console.log('[BLE] Found writable characteristic');
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (this.bleCharacteristic) break;
|
|
279
|
+
} catch {
|
|
280
|
+
console.warn('[BLE] Could not get characteristics for', svc.uuid);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Get write characteristic if not found yet
|
|
286
|
+
if (service && !this.bleCharacteristic) {
|
|
287
|
+
try {
|
|
288
|
+
this.bleCharacteristic = await service.getCharacteristic(
|
|
289
|
+
ZEBRA_WRITE_CHAR_UUID
|
|
290
|
+
);
|
|
291
|
+
console.log('[BLE] Found Zebra write characteristic');
|
|
292
|
+
} catch {
|
|
293
|
+
// Find any writable characteristic
|
|
294
|
+
const chars = await service.getCharacteristics();
|
|
295
|
+
for (const char of chars) {
|
|
296
|
+
if (char.properties.write || char.properties.writeWithoutResponse) {
|
|
297
|
+
this.bleCharacteristic = char;
|
|
298
|
+
console.log('[BLE] Found alternative write characteristic');
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (!this.bleCharacteristic) {
|
|
306
|
+
throw new Error('No writable characteristic found on printer');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Update connected printer state
|
|
310
|
+
this.connectedPrinter = {
|
|
311
|
+
name: device.name || 'Zebra Printer',
|
|
312
|
+
address: device.id,
|
|
313
|
+
type: 'bluetooth',
|
|
314
|
+
bleDevice: device,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Save to localStorage (without bleDevice reference)
|
|
318
|
+
this._saveToStorage(this.connectedPrinter);
|
|
319
|
+
|
|
320
|
+
// Listen for disconnection
|
|
321
|
+
device.addEventListener('gattserverdisconnected', () => {
|
|
322
|
+
console.log('[BLE] Disconnected');
|
|
323
|
+
this.bleDevice = null;
|
|
324
|
+
this.bleServer = null;
|
|
325
|
+
this.bleCharacteristic = null;
|
|
326
|
+
if (this.connectedPrinter?.type === 'bluetooth') {
|
|
327
|
+
this.connectedPrinter = null;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
success: true,
|
|
333
|
+
connected: true,
|
|
334
|
+
address: device.id,
|
|
335
|
+
type: 'bluetooth',
|
|
336
|
+
message: `Connected to ${device.name}`,
|
|
337
|
+
};
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error('[BLE] Connection failed:', error);
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
connected: false,
|
|
343
|
+
error: error.message || 'BLE connection failed',
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Send data to BLE printer in chunks
|
|
350
|
+
* @param {string} data - Data to send
|
|
351
|
+
* @returns {Promise<boolean>} True if data sent successfully
|
|
352
|
+
*/
|
|
353
|
+
async _sendBlePrint(data) {
|
|
354
|
+
if (!this.bleCharacteristic) {
|
|
355
|
+
console.error('[BLE] No characteristic available');
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const encoder = new TextEncoder();
|
|
361
|
+
const bytes = encoder.encode(data);
|
|
362
|
+
const MTU_SIZE = 20; // Standard BLE MTU
|
|
363
|
+
|
|
364
|
+
console.log('[BLE] Sending', bytes.length, 'bytes');
|
|
365
|
+
|
|
366
|
+
for (let offset = 0; offset < bytes.length; offset += MTU_SIZE) {
|
|
367
|
+
const chunk = bytes.slice(
|
|
368
|
+
offset,
|
|
369
|
+
Math.min(offset + MTU_SIZE, bytes.length)
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (this.bleCharacteristic.properties.writeWithoutResponse) {
|
|
373
|
+
await this.bleCharacteristic.writeValueWithoutResponse(chunk);
|
|
374
|
+
} else {
|
|
375
|
+
await this.bleCharacteristic.writeValue(chunk);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Small delay between chunks like iOS
|
|
379
|
+
if (offset + MTU_SIZE < bytes.length) {
|
|
380
|
+
await this._delay(50);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
console.log('[BLE] Data sent');
|
|
385
|
+
return true;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.error('[BLE] Send failed:', error);
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Disconnect BLE printer
|
|
394
|
+
*/
|
|
395
|
+
_disconnectBle() {
|
|
396
|
+
if (this.bleDevice?.gatt?.connected) {
|
|
397
|
+
this.bleDevice.gatt.disconnect();
|
|
398
|
+
}
|
|
399
|
+
this.bleDevice = null;
|
|
400
|
+
this.bleServer = null;
|
|
401
|
+
this.bleCharacteristic = null;
|
|
402
|
+
}
|
|
403
|
+
|
|
59
404
|
/**
|
|
60
405
|
* Fetch wrapper with error handling
|
|
61
406
|
* @param {string} endpoint - API route
|
|
@@ -142,16 +487,20 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
142
487
|
*/
|
|
143
488
|
async checkPermissions() {
|
|
144
489
|
const serviceOk = await this._checkService();
|
|
145
|
-
const
|
|
146
|
-
const bleSupported = hasNavigator && navigator.bluetooth !== undefined;
|
|
490
|
+
const bleSupported = this._isBleSupported();
|
|
147
491
|
|
|
148
|
-
|
|
149
|
-
|
|
492
|
+
// With BLE support, we can work even without printer-server
|
|
493
|
+
const hasPermissions = serviceOk || bleSupported;
|
|
494
|
+
|
|
495
|
+
if (!hasPermissions) {
|
|
496
|
+
console.warn(
|
|
497
|
+
'ZebraPrinter: no print method available (no printer-server, no BLE)'
|
|
498
|
+
);
|
|
150
499
|
return {
|
|
151
500
|
hasPermissions: false,
|
|
152
|
-
missingPermissions: ['printer-server'],
|
|
153
|
-
bluetoothSupported:
|
|
154
|
-
bleSupported:
|
|
501
|
+
missingPermissions: ['printer-server', 'bluetooth'],
|
|
502
|
+
bluetoothSupported: false,
|
|
503
|
+
bleSupported: false,
|
|
155
504
|
webMode: true,
|
|
156
505
|
serviceAvailable: false,
|
|
157
506
|
};
|
|
@@ -163,7 +512,10 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
163
512
|
bluetoothSupported: bleSupported,
|
|
164
513
|
bleSupported: bleSupported,
|
|
165
514
|
webMode: true,
|
|
166
|
-
serviceAvailable:
|
|
515
|
+
serviceAvailable: serviceOk,
|
|
516
|
+
// New: indicate which methods are available
|
|
517
|
+
tcpAvailable: serviceOk,
|
|
518
|
+
bleAvailable: bleSupported,
|
|
167
519
|
};
|
|
168
520
|
}
|
|
169
521
|
|
|
@@ -194,6 +546,29 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
194
546
|
|
|
195
547
|
const zplPayload = this._wrapTextIfNeeded(text);
|
|
196
548
|
|
|
549
|
+
// If connected via BLE, use BLE printing
|
|
550
|
+
if (this.connectedPrinter?.type === 'bluetooth' && this.bleCharacteristic) {
|
|
551
|
+
console.log('[BLE] Printing...');
|
|
552
|
+
const success = await this._sendBlePrint(zplPayload);
|
|
553
|
+
|
|
554
|
+
if (success) {
|
|
555
|
+
return {
|
|
556
|
+
success: true,
|
|
557
|
+
message: 'Print successful (BLE)',
|
|
558
|
+
zpl: `${zplPayload.slice(0, 100)}...`,
|
|
559
|
+
bytes: zplPayload.length,
|
|
560
|
+
method: 'bluetooth',
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
success: false,
|
|
566
|
+
message: 'BLE print failed',
|
|
567
|
+
error: 'Failed to send data to printer via Bluetooth',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Otherwise use TCP/IP via printer-server
|
|
197
572
|
const result = await this._fetch('/print', {
|
|
198
573
|
method: 'POST',
|
|
199
574
|
body: JSON.stringify({
|
|
@@ -208,6 +583,7 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
208
583
|
message: 'Print successful',
|
|
209
584
|
zpl: `${zplPayload.slice(0, 100)}...`,
|
|
210
585
|
bytes: zplPayload.length,
|
|
586
|
+
method: 'tcp',
|
|
211
587
|
};
|
|
212
588
|
}
|
|
213
589
|
|
|
@@ -230,6 +606,20 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
230
606
|
};
|
|
231
607
|
}
|
|
232
608
|
|
|
609
|
+
// BLE connection status
|
|
610
|
+
if (this.connectedPrinter.type === 'bluetooth') {
|
|
611
|
+
const isConnected =
|
|
612
|
+
this.bleDevice?.gatt?.connected === true &&
|
|
613
|
+
this.bleCharacteristic !== null;
|
|
614
|
+
return {
|
|
615
|
+
connected: isConnected,
|
|
616
|
+
status: isConnected ? 'connected' : 'disconnected',
|
|
617
|
+
printerAddress: this.connectedPrinter.address,
|
|
618
|
+
printerType: 'bluetooth',
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// TCP/IP status
|
|
233
623
|
const result = await this._fetch('/check', {
|
|
234
624
|
method: 'POST',
|
|
235
625
|
body: JSON.stringify({ ip: this.connectedPrinter.ip }),
|
|
@@ -256,11 +646,26 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
256
646
|
};
|
|
257
647
|
}
|
|
258
648
|
|
|
259
|
-
|
|
649
|
+
const statusCommand = '~HS';
|
|
650
|
+
|
|
651
|
+
// BLE status check
|
|
652
|
+
if (this.connectedPrinter.type === 'bluetooth' && this.bleCharacteristic) {
|
|
653
|
+
const success = await this._sendBlePrint(statusCommand);
|
|
654
|
+
return {
|
|
655
|
+
success,
|
|
656
|
+
message: success
|
|
657
|
+
? 'Status command sent (BLE)'
|
|
658
|
+
: 'Failed to send status command',
|
|
659
|
+
command: statusCommand,
|
|
660
|
+
method: 'bluetooth',
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// TCP/IP status check
|
|
260
665
|
const result = await this._fetch('/print', {
|
|
261
666
|
method: 'POST',
|
|
262
667
|
body: JSON.stringify({
|
|
263
|
-
zpl:
|
|
668
|
+
zpl: statusCommand,
|
|
264
669
|
ip: this.connectedPrinter.ip,
|
|
265
670
|
}),
|
|
266
671
|
});
|
|
@@ -268,36 +673,78 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
268
673
|
return {
|
|
269
674
|
success: result.success,
|
|
270
675
|
message: result.success ? 'Status command sent' : result.error,
|
|
271
|
-
command:
|
|
676
|
+
command: statusCommand,
|
|
677
|
+
method: 'tcp',
|
|
272
678
|
};
|
|
273
679
|
}
|
|
274
680
|
|
|
275
681
|
/**
|
|
276
682
|
* Scan for available printers
|
|
277
|
-
* @param {{subnet?: string}} [options] - Custom subnet
|
|
683
|
+
* @param {{subnet?: string, useBle?: boolean, skipCache?: boolean}} [options] - Custom subnet or BLE mode
|
|
278
684
|
* @returns {Promise<ScanResult>} List of found printers
|
|
279
685
|
*/
|
|
280
686
|
async scanForPrinters(options = {}) {
|
|
687
|
+
const { useBle, skipCache } = options;
|
|
281
688
|
const serviceOk = await this._checkService();
|
|
689
|
+
const bleSupported = this._isBleSupported();
|
|
282
690
|
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
691
|
+
// Check if last saved printer is still reachable (fast reconnect)
|
|
692
|
+
if (!skipCache && !useBle) {
|
|
693
|
+
const lastPrinter = this._loadFromStorage();
|
|
694
|
+
if (lastPrinter?.ip && serviceOk) {
|
|
695
|
+
console.log('[Cache] Checking:', lastPrinter.ip);
|
|
696
|
+
const check = await this._fetch('/check', {
|
|
697
|
+
method: 'POST',
|
|
698
|
+
body: JSON.stringify({ ip: lastPrinter.ip }),
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
if (check.reachable) {
|
|
702
|
+
console.log('[Cache] Printer reachable');
|
|
703
|
+
// Auto-connect to last printer
|
|
704
|
+
this.connectedPrinter = lastPrinter;
|
|
705
|
+
return {
|
|
706
|
+
success: true,
|
|
707
|
+
printers: [
|
|
708
|
+
{
|
|
709
|
+
name: lastPrinter.name || `Zebra @ ${lastPrinter.ip}`,
|
|
710
|
+
address: lastPrinter.ip,
|
|
711
|
+
type: 'network',
|
|
712
|
+
paired: true, // Mark as "paired" since it was saved
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
count: 1,
|
|
716
|
+
fromCache: true,
|
|
717
|
+
autoConnected: true,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
console.log('[Cache] Printer not reachable, scanning...');
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// If explicitly requesting BLE or printer-server not available
|
|
725
|
+
if (useBle || !serviceOk) {
|
|
726
|
+
if (!bleSupported) {
|
|
727
|
+
return {
|
|
728
|
+
success: false,
|
|
729
|
+
error:
|
|
730
|
+
'Web Bluetooth is not supported in this browser. Use Chrome or Edge.',
|
|
731
|
+
printers: [],
|
|
732
|
+
count: 0,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
console.log('[Web] Using BLE scan...');
|
|
737
|
+
return await this._scanBlePrinters();
|
|
291
738
|
}
|
|
292
739
|
|
|
293
740
|
const subnet = options.subnet || this.defaultSubnet || '192.168.1';
|
|
294
741
|
|
|
295
|
-
//
|
|
742
|
+
// Try TCP/IP scan first
|
|
296
743
|
const result = await this._fetch(
|
|
297
744
|
`/scan?subnet=${encodeURIComponent(subnet)}`
|
|
298
745
|
);
|
|
299
746
|
|
|
300
|
-
if (result.success) {
|
|
747
|
+
if (result.success && result.printers?.length > 0) {
|
|
301
748
|
return {
|
|
302
749
|
success: true,
|
|
303
750
|
printers: (result.printers || []).map((p) => ({
|
|
@@ -307,14 +754,30 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
307
754
|
paired: false,
|
|
308
755
|
})),
|
|
309
756
|
count: result.printers?.length || 0,
|
|
757
|
+
method: 'tcp',
|
|
310
758
|
};
|
|
311
759
|
}
|
|
312
760
|
|
|
761
|
+
// Fallback to BLE if TCP/IP found nothing and BLE is supported
|
|
762
|
+
if (bleSupported) {
|
|
763
|
+
console.log('[Web] TCP/IP found nothing, trying BLE...');
|
|
764
|
+
const bleResult = await this._scanBlePrinters();
|
|
765
|
+
|
|
766
|
+
if (bleResult.success) {
|
|
767
|
+
return {
|
|
768
|
+
...bleResult,
|
|
769
|
+
method: 'bluetooth',
|
|
770
|
+
tcpFallback: true,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
313
775
|
return {
|
|
314
776
|
success: false,
|
|
315
|
-
error: result.error || '
|
|
777
|
+
error: result.error || 'No printers found',
|
|
316
778
|
printers: [],
|
|
317
779
|
count: 0,
|
|
780
|
+
bleAvailable: bleSupported,
|
|
318
781
|
};
|
|
319
782
|
}
|
|
320
783
|
|
|
@@ -326,24 +789,45 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
326
789
|
async connect(options) {
|
|
327
790
|
const { address, type } = options || {};
|
|
328
791
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
792
|
+
// BLE connection (like iOS)
|
|
793
|
+
if (type === 'bluetooth') {
|
|
794
|
+
if (!this._isBleSupported()) {
|
|
795
|
+
return {
|
|
796
|
+
success: false,
|
|
797
|
+
connected: false,
|
|
798
|
+
error:
|
|
799
|
+
'Web Bluetooth is not supported in this browser. Use Chrome or Edge.',
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
console.log('[Web] Connecting via BLE...');
|
|
804
|
+
return await this._connectBle(address);
|
|
335
805
|
}
|
|
336
806
|
|
|
337
|
-
//
|
|
338
|
-
if (
|
|
807
|
+
// If no address provided but BLE devices were discovered, connect via BLE
|
|
808
|
+
if (!address && this.discoveredBleDevices.length > 0) {
|
|
809
|
+
console.log('[Web] No IP, using discovered BLE device...');
|
|
810
|
+
return await this._connectBle(this.discoveredBleDevices[0].id);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (!address) {
|
|
814
|
+
// No address and no BLE devices - try to scan BLE
|
|
815
|
+
if (this._isBleSupported()) {
|
|
816
|
+
console.log('[Web] No address, scanning BLE...');
|
|
817
|
+
const scanResult = await this._scanBlePrinters();
|
|
818
|
+
if (scanResult.success && scanResult.printers.length > 0) {
|
|
819
|
+
return await this._connectBle(scanResult.printers[0].address);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
339
823
|
return {
|
|
340
824
|
success: false,
|
|
341
825
|
connected: false,
|
|
342
|
-
error:
|
|
343
|
-
'Bluetooth is not available on web. Use IP address or test in the iOS app.',
|
|
826
|
+
error: 'Printer address (IP) not provided',
|
|
344
827
|
};
|
|
345
828
|
}
|
|
346
829
|
|
|
830
|
+
// TCP/IP connection via printer-server
|
|
347
831
|
const result = await this._fetch('/connect', {
|
|
348
832
|
method: 'POST',
|
|
349
833
|
body: JSON.stringify({ ip: address }),
|
|
@@ -356,6 +840,9 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
356
840
|
type: 'network',
|
|
357
841
|
};
|
|
358
842
|
|
|
843
|
+
// Save to localStorage for quick reconnect
|
|
844
|
+
this._saveToStorage(this.connectedPrinter);
|
|
845
|
+
|
|
359
846
|
return {
|
|
360
847
|
success: true,
|
|
361
848
|
connected: true,
|
|
@@ -365,6 +852,12 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
365
852
|
};
|
|
366
853
|
}
|
|
367
854
|
|
|
855
|
+
// If TCP connection failed and BLE is supported, offer BLE as fallback
|
|
856
|
+
if (this._isBleSupported()) {
|
|
857
|
+
console.log('[Web] TCP failed, trying BLE...');
|
|
858
|
+
return await this._connectBle(address);
|
|
859
|
+
}
|
|
860
|
+
|
|
368
861
|
return {
|
|
369
862
|
success: false,
|
|
370
863
|
connected: false,
|
|
@@ -411,9 +904,20 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
411
904
|
|
|
412
905
|
/**
|
|
413
906
|
* Disconnect from the printer
|
|
907
|
+
* @param {{clearSaved?: boolean}} [options] - Options for disconnect
|
|
414
908
|
* @returns {Promise<DisconnectResult>} Disconnect result
|
|
415
909
|
*/
|
|
416
|
-
async disconnect() {
|
|
910
|
+
async disconnect(options = {}) {
|
|
911
|
+
// Disconnect BLE if connected
|
|
912
|
+
if (this.connectedPrinter?.type === 'bluetooth') {
|
|
913
|
+
this._disconnectBle();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Optionally clear saved printer
|
|
917
|
+
if (options.clearSaved) {
|
|
918
|
+
this._saveToStorage(null);
|
|
919
|
+
}
|
|
920
|
+
|
|
417
921
|
this.connectedPrinter = null;
|
|
418
922
|
await Promise.resolve();
|
|
419
923
|
return {
|
|
@@ -438,7 +942,7 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
438
942
|
|
|
439
943
|
const result = await this._fetch('/network-info', { method: 'GET' });
|
|
440
944
|
|
|
441
|
-
if (result
|
|
945
|
+
if (result?.success) {
|
|
442
946
|
return {
|
|
443
947
|
supported: true,
|
|
444
948
|
type: result.type || 'unknown',
|
|
@@ -453,6 +957,335 @@ class ZebraPrinterWeb extends WebPlugin {
|
|
|
453
957
|
error: result?.error || 'Network info not available',
|
|
454
958
|
};
|
|
455
959
|
}
|
|
960
|
+
|
|
961
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
962
|
+
// DEV HELPER METHODS
|
|
963
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Get saved printer from localStorage
|
|
967
|
+
* @returns {Object|null} Saved printer settings
|
|
968
|
+
*/
|
|
969
|
+
getSavedPrinter() {
|
|
970
|
+
return this._loadFromStorage();
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Try to auto-connect to saved printer
|
|
975
|
+
* @returns {Promise<ConnectResult>} Connection result
|
|
976
|
+
*/
|
|
977
|
+
async autoConnect() {
|
|
978
|
+
const savedPrinter = this._loadFromStorage();
|
|
979
|
+
|
|
980
|
+
if (!savedPrinter) {
|
|
981
|
+
return {
|
|
982
|
+
success: false,
|
|
983
|
+
connected: false,
|
|
984
|
+
error: 'No saved printer found',
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
console.log('[AutoConnect] Trying:', savedPrinter.name);
|
|
989
|
+
|
|
990
|
+
// For BLE printers, need to re-scan and connect
|
|
991
|
+
if (savedPrinter.type === 'bluetooth') {
|
|
992
|
+
return await this.connect({
|
|
993
|
+
type: 'bluetooth',
|
|
994
|
+
address: savedPrinter.address,
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// For TCP/IP printers, check if reachable first
|
|
999
|
+
if (savedPrinter.ip) {
|
|
1000
|
+
const check = await this._fetch('/check', {
|
|
1001
|
+
method: 'POST',
|
|
1002
|
+
body: JSON.stringify({ ip: savedPrinter.ip }),
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
if (check.reachable) {
|
|
1006
|
+
this.connectedPrinter = savedPrinter;
|
|
1007
|
+
return {
|
|
1008
|
+
success: true,
|
|
1009
|
+
connected: true,
|
|
1010
|
+
address: savedPrinter.ip,
|
|
1011
|
+
type: 'network',
|
|
1012
|
+
message: 'Auto-connected to saved printer',
|
|
1013
|
+
fromCache: true,
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
success: false,
|
|
1020
|
+
connected: false,
|
|
1021
|
+
error: 'Saved printer not reachable',
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Show PWA-style printer picker dialog
|
|
1027
|
+
* Creates a native-feeling modal for printer selection
|
|
1028
|
+
* @param {{title?: string, scanOnOpen?: boolean}} [options] - Dialog options
|
|
1029
|
+
* @returns {Promise<{success: boolean, printer?: Object, cancelled?: boolean}>} Selected printer or cancellation
|
|
1030
|
+
*/
|
|
1031
|
+
async showPrinterPicker(options = {}) {
|
|
1032
|
+
const { title = 'Select Printer', scanOnOpen = true } = options;
|
|
1033
|
+
|
|
1034
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
1035
|
+
|
|
1036
|
+
// Create modal container
|
|
1037
|
+
const overlay = document.createElement('div');
|
|
1038
|
+
overlay.id = 'zebra-printer-picker';
|
|
1039
|
+
overlay.innerHTML = this._getPickerHTML(title);
|
|
1040
|
+
document.body.append(overlay);
|
|
1041
|
+
|
|
1042
|
+
// Store state for this picker instance
|
|
1043
|
+
const pickerState = {
|
|
1044
|
+
printers: [],
|
|
1045
|
+
resolve,
|
|
1046
|
+
cleanup: () => overlay.remove(),
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
// Setup event handlers
|
|
1050
|
+
this._setupPickerEvents(overlay, pickerState);
|
|
1051
|
+
|
|
1052
|
+
// Auto-scan if requested
|
|
1053
|
+
if (scanOnOpen) {
|
|
1054
|
+
await this._updatePickerPrinters(overlay, {});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
return promise;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Get picker HTML template
|
|
1062
|
+
* @private
|
|
1063
|
+
* @param {string} title - Dialog title
|
|
1064
|
+
* @returns {string} HTML string
|
|
1065
|
+
*/
|
|
1066
|
+
_getPickerHTML(title) {
|
|
1067
|
+
const bleButton = this._isBleSupported()
|
|
1068
|
+
? '<button class="zpp-btn zpp-btn-ble" data-action="ble">BLE</button>'
|
|
1069
|
+
: '';
|
|
1070
|
+
|
|
1071
|
+
return `
|
|
1072
|
+
<style>
|
|
1073
|
+
#zebra-printer-picker {
|
|
1074
|
+
position: fixed;
|
|
1075
|
+
inset: 0;
|
|
1076
|
+
z-index: 999999;
|
|
1077
|
+
display: flex;
|
|
1078
|
+
align-items: center;
|
|
1079
|
+
justify-content: center;
|
|
1080
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1081
|
+
backdrop-filter: blur(4px);
|
|
1082
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1083
|
+
}
|
|
1084
|
+
.zpp-modal {
|
|
1085
|
+
background: white;
|
|
1086
|
+
border-radius: 16px;
|
|
1087
|
+
width: 90%;
|
|
1088
|
+
max-width: 400px;
|
|
1089
|
+
max-height: 80vh;
|
|
1090
|
+
overflow: hidden;
|
|
1091
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
1092
|
+
animation: zpp-slideUp 0.3s ease-out;
|
|
1093
|
+
}
|
|
1094
|
+
@keyframes zpp-slideUp {
|
|
1095
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
1096
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1097
|
+
}
|
|
1098
|
+
.zpp-header {
|
|
1099
|
+
padding: 20px;
|
|
1100
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1101
|
+
display: flex;
|
|
1102
|
+
align-items: center;
|
|
1103
|
+
justify-content: space-between;
|
|
1104
|
+
}
|
|
1105
|
+
.zpp-header h2 { margin: 0; font-size: 18px; font-weight: 600; color: #111827; }
|
|
1106
|
+
.zpp-close { background: none; border: none; padding: 8px; cursor: pointer; border-radius: 8px; color: #6b7280; font-size: 18px; }
|
|
1107
|
+
.zpp-close:hover { background: #f3f4f6; }
|
|
1108
|
+
.zpp-content { padding: 16px; max-height: 400px; overflow-y: auto; }
|
|
1109
|
+
.zpp-loading { text-align: center; padding: 40px 20px; color: #6b7280; }
|
|
1110
|
+
.zpp-spinner {
|
|
1111
|
+
width: 40px; height: 40px;
|
|
1112
|
+
border: 3px solid #e5e7eb; border-top-color: #3b82f6;
|
|
1113
|
+
border-radius: 50%; animation: zpp-spin 1s linear infinite;
|
|
1114
|
+
margin: 0 auto 16px;
|
|
1115
|
+
}
|
|
1116
|
+
@keyframes zpp-spin { to { transform: rotate(360deg); } }
|
|
1117
|
+
.zpp-list { list-style: none; margin: 0; padding: 0; }
|
|
1118
|
+
.zpp-item {
|
|
1119
|
+
display: flex; align-items: center; padding: 16px;
|
|
1120
|
+
border: 1px solid #e5e7eb; border-radius: 12px;
|
|
1121
|
+
margin-bottom: 8px; cursor: pointer; transition: all 0.15s ease;
|
|
1122
|
+
}
|
|
1123
|
+
.zpp-item:hover { border-color: #3b82f6; background: #eff6ff; }
|
|
1124
|
+
.zpp-item.saved { border-color: #10b981; background: #ecfdf5; }
|
|
1125
|
+
.zpp-icon {
|
|
1126
|
+
width: 40px; height: 40px; background: #f3f4f6; border-radius: 10px;
|
|
1127
|
+
display: flex; align-items: center; justify-content: center;
|
|
1128
|
+
margin-right: 12px; font-size: 20px;
|
|
1129
|
+
}
|
|
1130
|
+
.zpp-info { flex: 1; }
|
|
1131
|
+
.zpp-name { font-weight: 500; color: #111827; margin-bottom: 2px; }
|
|
1132
|
+
.zpp-addr { font-size: 13px; color: #6b7280; }
|
|
1133
|
+
.zpp-badge { font-size: 11px; padding: 2px 8px; border-radius: 9999px; background: #10b981; color: white; margin-left: 8px; }
|
|
1134
|
+
.zpp-empty { text-align: center; padding: 40px 20px; color: #6b7280; }
|
|
1135
|
+
.zpp-actions { padding: 16px; border-top: 1px solid #e5e7eb; display: flex; gap: 8px; }
|
|
1136
|
+
.zpp-btn {
|
|
1137
|
+
flex: 1; padding: 12px 16px; border-radius: 10px;
|
|
1138
|
+
font-size: 14px; font-weight: 500; cursor: pointer;
|
|
1139
|
+
border: none; transition: all 0.15s ease;
|
|
1140
|
+
}
|
|
1141
|
+
.zpp-btn-secondary { background: #f3f4f6; color: #374151; }
|
|
1142
|
+
.zpp-btn-secondary:hover { background: #e5e7eb; }
|
|
1143
|
+
.zpp-btn-primary { background: #3b82f6; color: white; }
|
|
1144
|
+
.zpp-btn-primary:hover { background: #2563eb; }
|
|
1145
|
+
.zpp-btn-ble { background: #8b5cf6; color: white; }
|
|
1146
|
+
.zpp-btn-ble:hover { background: #7c3aed; }
|
|
1147
|
+
</style>
|
|
1148
|
+
<div class="zpp-modal">
|
|
1149
|
+
<div class="zpp-header">
|
|
1150
|
+
<h2>${title}</h2>
|
|
1151
|
+
<button class="zpp-close" data-action="close">✕</button>
|
|
1152
|
+
</div>
|
|
1153
|
+
<div class="zpp-content">
|
|
1154
|
+
<div class="zpp-loading">
|
|
1155
|
+
<div class="zpp-spinner"></div>
|
|
1156
|
+
<div>Searching for printers...</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
<div class="zpp-actions">
|
|
1160
|
+
<button class="zpp-btn zpp-btn-secondary" data-action="cancel">Cancel</button>
|
|
1161
|
+
<button class="zpp-btn zpp-btn-primary" data-action="rescan">Rescan</button>
|
|
1162
|
+
${bleButton}
|
|
1163
|
+
</div>
|
|
1164
|
+
</div>
|
|
1165
|
+
`;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Setup picker event handlers
|
|
1170
|
+
* @private
|
|
1171
|
+
* @param {HTMLElement} overlay - Picker overlay element
|
|
1172
|
+
* @param {Object} state - Picker state object
|
|
1173
|
+
*/
|
|
1174
|
+
_setupPickerEvents(overlay, state) {
|
|
1175
|
+
// Handle button clicks
|
|
1176
|
+
overlay.addEventListener('click', async (e) => {
|
|
1177
|
+
const action = e.target.dataset?.action;
|
|
1178
|
+
|
|
1179
|
+
if (action === 'close' || action === 'cancel') {
|
|
1180
|
+
state.cleanup();
|
|
1181
|
+
state.resolve({ success: false, cancelled: true });
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
if (action === 'rescan') {
|
|
1186
|
+
await this._updatePickerPrinters(overlay, { skipCache: true });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
if (action === 'ble') {
|
|
1191
|
+
await this._updatePickerPrinters(overlay, { useBle: true });
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Check if clicked on printer item
|
|
1196
|
+
const item = e.target.closest('.zpp-item');
|
|
1197
|
+
if (item) {
|
|
1198
|
+
const printer = {
|
|
1199
|
+
address: item.dataset.address,
|
|
1200
|
+
type: item.dataset.type,
|
|
1201
|
+
name: item.dataset.name,
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
state.cleanup();
|
|
1205
|
+
|
|
1206
|
+
// Auto-connect to selected printer
|
|
1207
|
+
const connectResult = await this.connect({
|
|
1208
|
+
address: printer.address,
|
|
1209
|
+
type: printer.type,
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
state.resolve({
|
|
1213
|
+
success: connectResult.success,
|
|
1214
|
+
printer: connectResult.success ? printer : undefined,
|
|
1215
|
+
error: connectResult.error,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// Close on overlay click (outside modal)
|
|
1221
|
+
overlay.addEventListener('click', (e) => {
|
|
1222
|
+
if (e.target === overlay) {
|
|
1223
|
+
state.cleanup();
|
|
1224
|
+
state.resolve({ success: false, cancelled: true });
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Update picker with scanned printers
|
|
1231
|
+
* @private
|
|
1232
|
+
* @param {HTMLElement} overlay - Picker overlay element
|
|
1233
|
+
* @param {Object} options - Scan options
|
|
1234
|
+
*/
|
|
1235
|
+
async _updatePickerPrinters(overlay, options) {
|
|
1236
|
+
const content = overlay.querySelector('.zpp-content');
|
|
1237
|
+
const loadingMsg = options.useBle
|
|
1238
|
+
? 'Scanning for Bluetooth printers...'
|
|
1239
|
+
: 'Searching for printers...';
|
|
1240
|
+
|
|
1241
|
+
content.innerHTML = `
|
|
1242
|
+
<div class="zpp-loading">
|
|
1243
|
+
<div class="zpp-spinner"></div>
|
|
1244
|
+
<div>${loadingMsg}</div>
|
|
1245
|
+
</div>
|
|
1246
|
+
`;
|
|
1247
|
+
|
|
1248
|
+
const result = await this.scanForPrinters(options);
|
|
1249
|
+
const printers = result.printers || [];
|
|
1250
|
+
|
|
1251
|
+
if (printers.length === 0) {
|
|
1252
|
+
content.innerHTML = `
|
|
1253
|
+
<div class="zpp-empty">
|
|
1254
|
+
<div style="font-size: 32px; margin-bottom: 16px; opacity: 0.5;">⎙</div>
|
|
1255
|
+
<div>No printers found</div>
|
|
1256
|
+
<div style="font-size: 13px; margin-top: 8px;">Make sure the printer is on and connected to the network</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
`;
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const savedPrinter = this._loadFromStorage();
|
|
1263
|
+
let html = '<ul class="zpp-list">';
|
|
1264
|
+
|
|
1265
|
+
for (const p of printers) {
|
|
1266
|
+
const isSaved =
|
|
1267
|
+
savedPrinter?.ip === p.address || savedPrinter?.address === p.address;
|
|
1268
|
+
const icon = p.type === 'bluetooth' ? 'BT' : 'IP';
|
|
1269
|
+
const typeLabel = p.type === 'bluetooth' ? 'Bluetooth' : 'Network';
|
|
1270
|
+
const badge = p.paired ? '<span class="zpp-badge">Saved</span>' : '';
|
|
1271
|
+
|
|
1272
|
+
html += `
|
|
1273
|
+
<li class="zpp-item ${isSaved ? 'saved' : ''}"
|
|
1274
|
+
data-address="${p.address}"
|
|
1275
|
+
data-type="${p.type}"
|
|
1276
|
+
data-name="${p.name}">
|
|
1277
|
+
<div class="zpp-icon">${icon}</div>
|
|
1278
|
+
<div class="zpp-info">
|
|
1279
|
+
<div class="zpp-name">${p.name}${badge}</div>
|
|
1280
|
+
<div class="zpp-addr">${p.address} • ${typeLabel}</div>
|
|
1281
|
+
</div>
|
|
1282
|
+
</li>
|
|
1283
|
+
`;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
html += '</ul>';
|
|
1287
|
+
content.innerHTML = html;
|
|
1288
|
+
}
|
|
456
1289
|
}
|
|
457
1290
|
|
|
458
1291
|
const ZebraPrinter = registerPlugin('ZebraPrinter', {
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sources":["../src/web/index.js","../src/index.js"],"sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\n/**\n * ZebraPrinterWeb - Web implementation for development\n *\n * Uses local printer-server.js (TCP/IP) so developers can\n * test the printer in the browser.\n *\n * Production (iOS) uses the native BLE implementation.\n */\n/**\n * @typedef {import('../definitions').EchoOptions} EchoOptions\n * @typedef {import('../definitions').EchoResult} EchoResult\n * @typedef {import('../definitions').PrintTextOptions} PrintTextOptions\n * @typedef {import('../definitions').PrintResult} PrintResult\n * @typedef {import('../definitions').PrinterStatus} PrinterStatus\n * @typedef {import('../definitions').PrinterStatusResult} PrinterStatusResult\n * @typedef {import('../definitions').ScanResult} ScanResult\n * @typedef {import('../definitions').ConnectOptions} ConnectOptions\n * @typedef {import('../definitions').ConnectResult} ConnectResult\n * @typedef {import('../definitions').DisconnectResult} DisconnectResult\n * @typedef {import('../definitions').PermissionResult} PermissionResult\n */\n\nconst API_BASE = '/api/printer';\n\nexport class ZebraPrinterWeb extends WebPlugin {\n constructor() {\n super();\n this.connectedPrinter = null;\n this.serviceAvailable = null;\n this.defaultSubnet = this._getDefaultSubnet();\n }\n\n /**\n * Check if printer-server is reachable\n * @returns {Promise<boolean>} True if reachable\n */\n async _checkService() {\n if (this.serviceAvailable !== null) {\n return this.serviceAvailable;\n }\n\n try {\n const response = await fetch(`${API_BASE}/status`, {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' },\n });\n const data = await response.json();\n this.serviceAvailable = data.success === true;\n return this.serviceAvailable;\n } catch (error) {\n console.warn('ZebraPrinter: service check failed', error);\n this.serviceAvailable = false;\n return false;\n }\n }\n\n /**\n * Fetch wrapper with error handling\n * @param {string} endpoint - API route\n * @param {RequestInit} [options] - Fetch configuration\n * @returns {Promise<any>} Response JSON\n */\n async _fetch(endpoint, options = {}) {\n try {\n const response = await fetch(`${API_BASE}${endpoint}`, {\n headers: { 'Content-Type': 'application/json' },\n ...options,\n });\n return await response.json();\n } catch (error) {\n console.warn(`ZebraPrinter Web: ${endpoint} failed -`, error.message);\n return {\n success: false,\n error:\n 'Printer service is not available. Ensure Vite dev server runs with printer-server plugin.',\n serviceUnavailable: true,\n };\n }\n }\n\n /**\n * Derive default subnet from host IP, if possible\n * @returns {string} Example: \"192.168.1\" or \"10.0.0\"\n */\n _getDefaultSubnet() {\n if (typeof window === 'undefined') return '192.168.1';\n const host = window.location.hostname;\n const ipMatch = host.match(\n /^(10|172\\.(1[6-9]|2[0-9]|3[0-1])|192\\.168)\\.(\\d+)\\.(\\d+)$/\n );\n if (ipMatch) {\n return `${ipMatch[1]}.${ipMatch[3]}`;\n }\n return '192.168.1';\n }\n\n /**\n * Wrap plain text into a basic ZPL label unless it already looks like ZPL\n * @param {string} text - Input text or ZPL\n * @returns {string} ZPL payload\n */\n _wrapTextIfNeeded(text) {\n const isZplLike =\n typeof text === 'string' &&\n /\\^XA/i.test(text) &&\n (/\\^XZ/i.test(text) || /\\^JUS/i.test(text));\n if (isZplLike) {\n return text;\n }\n\n const safeText = String(text ?? '').replaceAll(/\\r?\\n/g, String.raw`\\&`);\n return [\n '^XA',\n '^CI28',\n '^FO50,50',\n '^A0N,30,30',\n `^FD${safeText}^FS`,\n '^XZ',\n ].join('\\n');\n }\n\n /**\n * Echo a value\n * @param {EchoOptions} options - Input data\n * @returns {Promise<EchoResult & {platform: string, serviceAvailable: boolean}>} Echo result with platform info\n */\n async echo(options) {\n console.log('ZebraPrinter: echo called with options:', options);\n const serviceOk = await this._checkService();\n return {\n value: options.value,\n platform: 'web',\n serviceAvailable: serviceOk,\n };\n }\n\n /**\n * Check if required permissions are granted\n * @returns {Promise<PermissionResult>} Permission status\n */\n async checkPermissions() {\n const serviceOk = await this._checkService();\n const hasNavigator = typeof navigator !== 'undefined';\n const bleSupported = hasNavigator && navigator.bluetooth !== undefined;\n\n if (!serviceOk) {\n console.warn('ZebraPrinter: printer-server not available');\n return {\n hasPermissions: false,\n missingPermissions: ['printer-server'],\n bluetoothSupported: bleSupported,\n bleSupported: bleSupported,\n webMode: true,\n serviceAvailable: false,\n };\n }\n\n return {\n hasPermissions: true,\n missingPermissions: [],\n bluetoothSupported: bleSupported,\n bleSupported: bleSupported,\n webMode: true,\n serviceAvailable: true,\n };\n }\n\n /**\n * Request required permissions\n * @returns {Promise<PermissionResult>} Permission status\n */\n async requestPermissions() {\n // Not needed on web - just check service\n return await this.checkPermissions();\n }\n\n /**\n * Print text to the connected printer\n * @param {PrintTextOptions} options - Parameters with ZPL text\n * @returns {Promise<PrintResult>} Print result\n */\n async printText(options) {\n const { text } = options || {};\n\n if (!text) {\n return {\n success: false,\n message: 'No text provided for printing',\n error: 'Missing text parameter',\n };\n }\n\n const zplPayload = this._wrapTextIfNeeded(text);\n\n const result = await this._fetch('/print', {\n method: 'POST',\n body: JSON.stringify({\n zpl: zplPayload,\n ip: this.connectedPrinter?.ip,\n }),\n });\n\n if (result.success) {\n return {\n success: true,\n message: 'Print successful',\n zpl: `${zplPayload.slice(0, 100)}...`,\n bytes: zplPayload.length,\n };\n }\n\n return {\n success: false,\n message: result.error || 'Print failed',\n error: result.error,\n };\n }\n\n /**\n * Get printer status\n * @returns {Promise<PrinterStatus>} Printer status\n */\n async getStatus() {\n if (!this.connectedPrinter) {\n return {\n connected: false,\n status: 'disconnected',\n };\n }\n\n const result = await this._fetch('/check', {\n method: 'POST',\n body: JSON.stringify({ ip: this.connectedPrinter.ip }),\n });\n\n return {\n connected: result.reachable === true,\n status: result.reachable ? 'connected' : 'disconnected',\n printerAddress: this.connectedPrinter.ip,\n printerType: 'network',\n };\n }\n\n /**\n * Check printer status with ZPL command\n * @returns {Promise<PrinterStatusResult>} Status request result\n */\n async checkPrinterStatus() {\n if (!this.connectedPrinter) {\n return {\n success: false,\n message: 'No printer connected',\n error: 'Not connected',\n };\n }\n\n // On web we send ~HS command\n const result = await this._fetch('/print', {\n method: 'POST',\n body: JSON.stringify({\n zpl: '~HS',\n ip: this.connectedPrinter.ip,\n }),\n });\n\n return {\n success: result.success,\n message: result.success ? 'Status command sent' : result.error,\n command: '~HS',\n };\n }\n\n /**\n * Scan for available printers\n * @param {{subnet?: string}} [options] - Custom subnet (e.g., \"192.168.0\")\n * @returns {Promise<ScanResult>} List of found printers\n */\n async scanForPrinters(options = {}) {\n const serviceOk = await this._checkService();\n\n if (!serviceOk) {\n return {\n success: false,\n error:\n 'Printer service is not available. Add printer-server.js and vite-plugin-zebra-printer.js to your project.',\n printers: [],\n count: 0,\n };\n }\n\n const subnet = options.subnet || this.defaultSubnet || '192.168.1';\n\n // Scan the network (can be slow)\n const result = await this._fetch(\n `/scan?subnet=${encodeURIComponent(subnet)}`\n );\n\n if (result.success) {\n return {\n success: true,\n printers: (result.printers || []).map((p) => ({\n name: p.name || `Zebra @ ${p.ip}`,\n address: p.ip || p.address,\n type: 'network',\n paired: false,\n })),\n count: result.printers?.length || 0,\n };\n }\n\n return {\n success: false,\n error: result.error || 'Scan failed',\n printers: [],\n count: 0,\n };\n }\n\n /**\n * Connect to a printer\n * @param {ConnectOptions} options - Connection parameters\n * @returns {Promise<ConnectResult>} Connection result\n */\n async connect(options) {\n const { address, type } = options || {};\n\n if (!address) {\n return {\n success: false,\n connected: false,\n error: 'Printer address (IP) not provided',\n };\n }\n\n // On web we support only network connections\n if (type === 'bluetooth') {\n return {\n success: false,\n connected: false,\n error:\n 'Bluetooth is not available on web. Use IP address or test in the iOS app.',\n };\n }\n\n const result = await this._fetch('/connect', {\n method: 'POST',\n body: JSON.stringify({ ip: address }),\n });\n\n if (result.success) {\n this.connectedPrinter = {\n ip: address,\n name: result.printer?.name || `Zebra @ ${address}`,\n type: 'network',\n };\n\n return {\n success: true,\n connected: true,\n address,\n type: 'network',\n message: 'Connected to printer',\n };\n }\n\n return {\n success: false,\n connected: false,\n error: result.error || 'Connection failed',\n };\n }\n\n /**\n * Connect to printer by MAC address (web - redirect to IP)\n * @param {ConnectOptions} options - Connection parameters\n * @returns {Promise<ConnectResult>} Connection result\n */\n async connectByAddress(options) {\n console.warn('ZebraPrinter Web: connectByAddress - use IP address on web');\n return await this.connect(options);\n }\n\n /**\n * Check device by address\n * @param {ConnectOptions} options - Parameters with address\n * @returns {Promise<ConnectResult>} Reachability info\n */\n async checkDeviceByAddress(options) {\n const { address } = options || {};\n\n if (!address) {\n return {\n success: false,\n error: 'Address not provided',\n };\n }\n\n const result = await this._fetch('/check', {\n method: 'POST',\n body: JSON.stringify({ ip: address }),\n });\n\n return {\n success: true,\n reachable: result.reachable,\n address,\n };\n }\n\n /**\n * Disconnect from the printer\n * @returns {Promise<DisconnectResult>} Disconnect result\n */\n async disconnect() {\n this.connectedPrinter = null;\n await Promise.resolve();\n return {\n success: true,\n connected: false,\n };\n }\n\n /**\n * Get device network info (web not supported directly)\n * @returns {Promise<{supported: boolean, type: string, error?: string, ip?: string, ssid?: string}>} Network info proxied via printer-server if available\n */\n async getNetworkInfo() {\n const serviceOk = await this._checkService();\n if (!serviceOk) {\n return {\n supported: false,\n type: 'unknown',\n error: 'Printer service not available for network info',\n };\n }\n\n const result = await this._fetch('/network-info', { method: 'GET' });\n\n if (result && result.success) {\n return {\n supported: true,\n type: result.type || 'unknown',\n ip: result.ip,\n ssid: result.ssid,\n };\n }\n\n return {\n supported: false,\n type: 'unknown',\n error: result?.error || 'Network info not available',\n };\n }\n}\n","import { registerPlugin } from '@capacitor/core';\n\nimport { ZebraPrinterWeb } from './web';\n\nconst ZebraPrinter = registerPlugin('ZebraPrinter', {\n web: () => new ZebraPrinterWeb(),\n // iOS and Android plugins will be auto-discovered via the Capacitor config\n});\n\nexport { ZebraPrinter };\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../src/web/index.js","../src/index.js"],"sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\n/**\n * ZebraPrinterWeb - Web implementation for development\n *\n * Uses local printer-server.js (TCP/IP) or Web Bluetooth API.\n * Falls back to BLE when TCP/IP is not available (like iOS).\n *\n * Production (iOS) uses the native BLE implementation.\n */\n/**\n * @typedef {import('../definitions').EchoOptions} EchoOptions\n * @typedef {import('../definitions').EchoResult} EchoResult\n * @typedef {import('../definitions').PrintTextOptions} PrintTextOptions\n * @typedef {import('../definitions').PrintResult} PrintResult\n * @typedef {import('../definitions').PrinterStatus} PrinterStatus\n * @typedef {import('../definitions').PrinterStatusResult} PrinterStatusResult\n * @typedef {import('../definitions').ScanResult} ScanResult\n * @typedef {import('../definitions').ConnectOptions} ConnectOptions\n * @typedef {import('../definitions').ConnectResult} ConnectResult\n * @typedef {import('../definitions').DisconnectResult} DisconnectResult\n * @typedef {import('../definitions').PermissionResult} PermissionResult\n */\n\nconst API_BASE = '/api/printer';\n\n// Zebra printer BLE service UUIDs\nconst ZEBRA_SERVICE_UUID = '38eb4a80-c570-11e3-9507-0002a5d5c51b';\nconst ZEBRA_WRITE_CHAR_UUID = '38eb4a82-c570-11e3-9507-0002a5d5c51b';\n// Alternative service for some Zebra models\nconst ZEBRA_SPP_SERVICE_UUID = '00001101-0000-1000-8000-00805f9b34fb';\n\n// LocalStorage key for persisting printer settings\nconst STORAGE_KEY = 'zebra_printer_settings';\n\nexport class ZebraPrinterWeb extends WebPlugin {\n constructor() {\n super();\n this.serviceAvailable = null;\n this.defaultSubnet = this._getDefaultSubnet();\n // BLE state\n this.bleDevice = null;\n this.bleServer = null;\n this.bleCharacteristic = null;\n this.discoveredBleDevices = [];\n // Load last printer from localStorage\n this.connectedPrinter = this._loadFromStorage();\n if (this.connectedPrinter) {\n console.log(\n '[Storage] Loaded:',\n this.connectedPrinter.name || this.connectedPrinter.ip\n );\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // LOCAL STORAGE PERSISTENCE\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Load printer settings from localStorage\n * @returns {Object|null} Saved printer settings or null\n */\n _loadFromStorage() {\n try {\n if (typeof localStorage === 'undefined') return null;\n const saved = localStorage.getItem(STORAGE_KEY);\n return saved ? JSON.parse(saved) : null;\n } catch {\n return null;\n }\n }\n\n /**\n * Save printer settings to localStorage\n * @param {Object} printer - Printer settings to save\n */\n _saveToStorage(printer) {\n try {\n if (typeof localStorage === 'undefined') return;\n if (printer) {\n // Don't save BLE device reference (can't be serialized)\n const toSave = { ...printer };\n delete toSave.bleDevice;\n localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));\n console.log('[Storage] Saved:', toSave.name || toSave.ip);\n } else {\n localStorage.removeItem(STORAGE_KEY);\n console.log('[Storage] Cleared');\n }\n } catch {\n // Ignore storage errors\n }\n }\n\n /**\n * Clear saved printer from localStorage\n * @returns {{success: boolean, message: string}} Result object\n */\n clearSavedPrinter() {\n this._saveToStorage(null);\n return { success: true, message: 'Saved printer cleared' };\n }\n\n /**\n * Check if Web Bluetooth is supported\n * @returns {boolean} True if Web Bluetooth API is available\n */\n _isBleSupported() {\n return (\n typeof navigator !== 'undefined' && navigator.bluetooth !== undefined\n );\n }\n\n /**\n * Delay helper for BLE chunk sending\n * @param {number} ms - Milliseconds to delay\n * @returns {Promise<void>} Resolves after delay\n */\n _delay(ms) {\n const { promise, resolve } = Promise.withResolvers();\n setTimeout(resolve, ms);\n return promise;\n }\n\n /**\n * Check if printer-server is reachable\n * @returns {Promise<boolean>} True if reachable\n */\n async _checkService() {\n if (this.serviceAvailable !== null) {\n return this.serviceAvailable;\n }\n\n try {\n const response = await fetch(`${API_BASE}/status`, {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' },\n });\n const data = await response.json();\n this.serviceAvailable = data.success === true;\n return this.serviceAvailable;\n } catch (error) {\n console.warn('ZebraPrinter: service check failed', error);\n this.serviceAvailable = false;\n return false;\n }\n }\n\n /**\n * Scan for BLE printers using Web Bluetooth API\n * @returns {Promise<ScanResult>} List of discovered BLE printers\n */\n async _scanBlePrinters() {\n if (!this._isBleSupported()) {\n return {\n success: false,\n error: 'Web Bluetooth is not supported in this browser',\n printers: [],\n count: 0,\n };\n }\n\n try {\n console.log('[BLE] Scanning...');\n\n // Request device with Zebra name prefixes\n const device = await navigator.bluetooth.requestDevice({\n filters: [\n { namePrefix: 'Zebra' },\n { namePrefix: 'ZT' },\n { namePrefix: 'ZD' },\n { namePrefix: 'ZQ' },\n { namePrefix: 'XXZHN' },\n ],\n optionalServices: [ZEBRA_SERVICE_UUID, ZEBRA_SPP_SERVICE_UUID],\n });\n\n console.log('[BLE] Found:', device.name);\n\n // Store discovered device\n if (!this.discoveredBleDevices.some((d) => d.id === device.id)) {\n this.discoveredBleDevices.push(device);\n }\n\n return {\n success: true,\n printers: this.discoveredBleDevices.map((d) => ({\n name: d.name || 'Zebra Printer',\n address: d.id,\n type: 'bluetooth',\n paired: false,\n })),\n count: this.discoveredBleDevices.length,\n };\n } catch (error) {\n console.warn('[BLE] Scan failed:', error.message);\n return {\n success: false,\n error: error.message || 'BLE scan failed',\n printers: [],\n count: 0,\n };\n }\n }\n\n /**\n * Connect to a BLE printer\n * @param {string} deviceId - Device ID from scan\n * @returns {Promise<ConnectResult>} Connection result with success status\n */\n async _connectBle(deviceId) {\n if (!this._isBleSupported()) {\n return {\n success: false,\n connected: false,\n error: 'Web Bluetooth is not supported',\n };\n }\n\n try {\n let device = this.discoveredBleDevices.find((d) => d.id === deviceId);\n\n // If device not found, request new device\n if (!device) {\n console.log('[BLE] Device not cached, requesting...');\n device = await navigator.bluetooth.requestDevice({\n filters: [\n { namePrefix: 'Zebra' },\n { namePrefix: 'ZT' },\n { namePrefix: 'ZD' },\n { namePrefix: 'ZQ' },\n { namePrefix: 'XXZHN' },\n ],\n optionalServices: [ZEBRA_SERVICE_UUID, ZEBRA_SPP_SERVICE_UUID],\n });\n this.discoveredBleDevices.push(device);\n }\n\n console.log('[BLE] Connecting to', device.name);\n\n // Connect to GATT server\n this.bleServer = await device.gatt.connect();\n this.bleDevice = device;\n\n console.log('[BLE] GATT connected');\n\n // Try to get Zebra service\n let service;\n try {\n service = await this.bleServer.getPrimaryService(ZEBRA_SERVICE_UUID);\n console.log('[BLE] Found Zebra service');\n } catch {\n // Try alternative service\n console.log('[BLE] Trying alternative services...');\n const services = await this.bleServer.getPrimaryServices();\n console.log('[BLE] Found', services.length, 'services');\n\n for (const svc of services) {\n console.log(` - Service: ${svc.uuid}`);\n try {\n const chars = await svc.getCharacteristics();\n for (const char of chars) {\n console.log(\n ` - Characteristic: ${char.uuid}, props:`,\n char.properties\n );\n if (\n char.properties.write ||\n char.properties.writeWithoutResponse\n ) {\n service = svc;\n this.bleCharacteristic = char;\n console.log('[BLE] Found writable characteristic');\n break;\n }\n }\n if (this.bleCharacteristic) break;\n } catch {\n console.warn('[BLE] Could not get characteristics for', svc.uuid);\n }\n }\n }\n\n // Get write characteristic if not found yet\n if (service && !this.bleCharacteristic) {\n try {\n this.bleCharacteristic = await service.getCharacteristic(\n ZEBRA_WRITE_CHAR_UUID\n );\n console.log('[BLE] Found Zebra write characteristic');\n } catch {\n // Find any writable characteristic\n const chars = await service.getCharacteristics();\n for (const char of chars) {\n if (char.properties.write || char.properties.writeWithoutResponse) {\n this.bleCharacteristic = char;\n console.log('[BLE] Found alternative write characteristic');\n break;\n }\n }\n }\n }\n\n if (!this.bleCharacteristic) {\n throw new Error('No writable characteristic found on printer');\n }\n\n // Update connected printer state\n this.connectedPrinter = {\n name: device.name || 'Zebra Printer',\n address: device.id,\n type: 'bluetooth',\n bleDevice: device,\n };\n\n // Save to localStorage (without bleDevice reference)\n this._saveToStorage(this.connectedPrinter);\n\n // Listen for disconnection\n device.addEventListener('gattserverdisconnected', () => {\n console.log('[BLE] Disconnected');\n this.bleDevice = null;\n this.bleServer = null;\n this.bleCharacteristic = null;\n if (this.connectedPrinter?.type === 'bluetooth') {\n this.connectedPrinter = null;\n }\n });\n\n return {\n success: true,\n connected: true,\n address: device.id,\n type: 'bluetooth',\n message: `Connected to ${device.name}`,\n };\n } catch (error) {\n console.error('[BLE] Connection failed:', error);\n return {\n success: false,\n connected: false,\n error: error.message || 'BLE connection failed',\n };\n }\n }\n\n /**\n * Send data to BLE printer in chunks\n * @param {string} data - Data to send\n * @returns {Promise<boolean>} True if data sent successfully\n */\n async _sendBlePrint(data) {\n if (!this.bleCharacteristic) {\n console.error('[BLE] No characteristic available');\n return false;\n }\n\n try {\n const encoder = new TextEncoder();\n const bytes = encoder.encode(data);\n const MTU_SIZE = 20; // Standard BLE MTU\n\n console.log('[BLE] Sending', bytes.length, 'bytes');\n\n for (let offset = 0; offset < bytes.length; offset += MTU_SIZE) {\n const chunk = bytes.slice(\n offset,\n Math.min(offset + MTU_SIZE, bytes.length)\n );\n\n if (this.bleCharacteristic.properties.writeWithoutResponse) {\n await this.bleCharacteristic.writeValueWithoutResponse(chunk);\n } else {\n await this.bleCharacteristic.writeValue(chunk);\n }\n\n // Small delay between chunks like iOS\n if (offset + MTU_SIZE < bytes.length) {\n await this._delay(50);\n }\n }\n\n console.log('[BLE] Data sent');\n return true;\n } catch (error) {\n console.error('[BLE] Send failed:', error);\n return false;\n }\n }\n\n /**\n * Disconnect BLE printer\n */\n _disconnectBle() {\n if (this.bleDevice?.gatt?.connected) {\n this.bleDevice.gatt.disconnect();\n }\n this.bleDevice = null;\n this.bleServer = null;\n this.bleCharacteristic = null;\n }\n\n /**\n * Fetch wrapper with error handling\n * @param {string} endpoint - API route\n * @param {RequestInit} [options] - Fetch configuration\n * @returns {Promise<any>} Response JSON\n */\n async _fetch(endpoint, options = {}) {\n try {\n const response = await fetch(`${API_BASE}${endpoint}`, {\n headers: { 'Content-Type': 'application/json' },\n ...options,\n });\n return await response.json();\n } catch (error) {\n console.warn(`ZebraPrinter Web: ${endpoint} failed -`, error.message);\n return {\n success: false,\n error:\n 'Printer service is not available. Ensure Vite dev server runs with printer-server plugin.',\n serviceUnavailable: true,\n };\n }\n }\n\n /**\n * Derive default subnet from host IP, if possible\n * @returns {string} Example: \"192.168.1\" or \"10.0.0\"\n */\n _getDefaultSubnet() {\n if (typeof window === 'undefined') return '192.168.1';\n const host = window.location.hostname;\n const ipMatch = host.match(\n /^(10|172\\.(1[6-9]|2[0-9]|3[0-1])|192\\.168)\\.(\\d+)\\.(\\d+)$/\n );\n if (ipMatch) {\n return `${ipMatch[1]}.${ipMatch[3]}`;\n }\n return '192.168.1';\n }\n\n /**\n * Wrap plain text into a basic ZPL label unless it already looks like ZPL\n * @param {string} text - Input text or ZPL\n * @returns {string} ZPL payload\n */\n _wrapTextIfNeeded(text) {\n const isZplLike =\n typeof text === 'string' &&\n /\\^XA/i.test(text) &&\n (/\\^XZ/i.test(text) || /\\^JUS/i.test(text));\n if (isZplLike) {\n return text;\n }\n\n const safeText = String(text ?? '').replaceAll(/\\r?\\n/g, String.raw`\\&`);\n return [\n '^XA',\n '^CI28',\n '^FO50,50',\n '^A0N,30,30',\n `^FD${safeText}^FS`,\n '^XZ',\n ].join('\\n');\n }\n\n /**\n * Echo a value\n * @param {EchoOptions} options - Input data\n * @returns {Promise<EchoResult & {platform: string, serviceAvailable: boolean}>} Echo result with platform info\n */\n async echo(options) {\n console.log('ZebraPrinter: echo called with options:', options);\n const serviceOk = await this._checkService();\n return {\n value: options.value,\n platform: 'web',\n serviceAvailable: serviceOk,\n };\n }\n\n /**\n * Check if required permissions are granted\n * @returns {Promise<PermissionResult>} Permission status\n */\n async checkPermissions() {\n const serviceOk = await this._checkService();\n const bleSupported = this._isBleSupported();\n\n // With BLE support, we can work even without printer-server\n const hasPermissions = serviceOk || bleSupported;\n\n if (!hasPermissions) {\n console.warn(\n 'ZebraPrinter: no print method available (no printer-server, no BLE)'\n );\n return {\n hasPermissions: false,\n missingPermissions: ['printer-server', 'bluetooth'],\n bluetoothSupported: false,\n bleSupported: false,\n webMode: true,\n serviceAvailable: false,\n };\n }\n\n return {\n hasPermissions: true,\n missingPermissions: [],\n bluetoothSupported: bleSupported,\n bleSupported: bleSupported,\n webMode: true,\n serviceAvailable: serviceOk,\n // New: indicate which methods are available\n tcpAvailable: serviceOk,\n bleAvailable: bleSupported,\n };\n }\n\n /**\n * Request required permissions\n * @returns {Promise<PermissionResult>} Permission status\n */\n async requestPermissions() {\n // Not needed on web - just check service\n return await this.checkPermissions();\n }\n\n /**\n * Print text to the connected printer\n * @param {PrintTextOptions} options - Parameters with ZPL text\n * @returns {Promise<PrintResult>} Print result\n */\n async printText(options) {\n const { text } = options || {};\n\n if (!text) {\n return {\n success: false,\n message: 'No text provided for printing',\n error: 'Missing text parameter',\n };\n }\n\n const zplPayload = this._wrapTextIfNeeded(text);\n\n // If connected via BLE, use BLE printing\n if (this.connectedPrinter?.type === 'bluetooth' && this.bleCharacteristic) {\n console.log('[BLE] Printing...');\n const success = await this._sendBlePrint(zplPayload);\n\n if (success) {\n return {\n success: true,\n message: 'Print successful (BLE)',\n zpl: `${zplPayload.slice(0, 100)}...`,\n bytes: zplPayload.length,\n method: 'bluetooth',\n };\n }\n\n return {\n success: false,\n message: 'BLE print failed',\n error: 'Failed to send data to printer via Bluetooth',\n };\n }\n\n // Otherwise use TCP/IP via printer-server\n const result = await this._fetch('/print', {\n method: 'POST',\n body: JSON.stringify({\n zpl: zplPayload,\n ip: this.connectedPrinter?.ip,\n }),\n });\n\n if (result.success) {\n return {\n success: true,\n message: 'Print successful',\n zpl: `${zplPayload.slice(0, 100)}...`,\n bytes: zplPayload.length,\n method: 'tcp',\n };\n }\n\n return {\n success: false,\n message: result.error || 'Print failed',\n error: result.error,\n };\n }\n\n /**\n * Get printer status\n * @returns {Promise<PrinterStatus>} Printer status\n */\n async getStatus() {\n if (!this.connectedPrinter) {\n return {\n connected: false,\n status: 'disconnected',\n };\n }\n\n // BLE connection status\n if (this.connectedPrinter.type === 'bluetooth') {\n const isConnected =\n this.bleDevice?.gatt?.connected === true &&\n this.bleCharacteristic !== null;\n return {\n connected: isConnected,\n status: isConnected ? 'connected' : 'disconnected',\n printerAddress: this.connectedPrinter.address,\n printerType: 'bluetooth',\n };\n }\n\n // TCP/IP status\n const result = await this._fetch('/check', {\n method: 'POST',\n body: JSON.stringify({ ip: this.connectedPrinter.ip }),\n });\n\n return {\n connected: result.reachable === true,\n status: result.reachable ? 'connected' : 'disconnected',\n printerAddress: this.connectedPrinter.ip,\n printerType: 'network',\n };\n }\n\n /**\n * Check printer status with ZPL command\n * @returns {Promise<PrinterStatusResult>} Status request result\n */\n async checkPrinterStatus() {\n if (!this.connectedPrinter) {\n return {\n success: false,\n message: 'No printer connected',\n error: 'Not connected',\n };\n }\n\n const statusCommand = '~HS';\n\n // BLE status check\n if (this.connectedPrinter.type === 'bluetooth' && this.bleCharacteristic) {\n const success = await this._sendBlePrint(statusCommand);\n return {\n success,\n message: success\n ? 'Status command sent (BLE)'\n : 'Failed to send status command',\n command: statusCommand,\n method: 'bluetooth',\n };\n }\n\n // TCP/IP status check\n const result = await this._fetch('/print', {\n method: 'POST',\n body: JSON.stringify({\n zpl: statusCommand,\n ip: this.connectedPrinter.ip,\n }),\n });\n\n return {\n success: result.success,\n message: result.success ? 'Status command sent' : result.error,\n command: statusCommand,\n method: 'tcp',\n };\n }\n\n /**\n * Scan for available printers\n * @param {{subnet?: string, useBle?: boolean, skipCache?: boolean}} [options] - Custom subnet or BLE mode\n * @returns {Promise<ScanResult>} List of found printers\n */\n async scanForPrinters(options = {}) {\n const { useBle, skipCache } = options;\n const serviceOk = await this._checkService();\n const bleSupported = this._isBleSupported();\n\n // Check if last saved printer is still reachable (fast reconnect)\n if (!skipCache && !useBle) {\n const lastPrinter = this._loadFromStorage();\n if (lastPrinter?.ip && serviceOk) {\n console.log('[Cache] Checking:', lastPrinter.ip);\n const check = await this._fetch('/check', {\n method: 'POST',\n body: JSON.stringify({ ip: lastPrinter.ip }),\n });\n\n if (check.reachable) {\n console.log('[Cache] Printer reachable');\n // Auto-connect to last printer\n this.connectedPrinter = lastPrinter;\n return {\n success: true,\n printers: [\n {\n name: lastPrinter.name || `Zebra @ ${lastPrinter.ip}`,\n address: lastPrinter.ip,\n type: 'network',\n paired: true, // Mark as \"paired\" since it was saved\n },\n ],\n count: 1,\n fromCache: true,\n autoConnected: true,\n };\n }\n console.log('[Cache] Printer not reachable, scanning...');\n }\n }\n\n // If explicitly requesting BLE or printer-server not available\n if (useBle || !serviceOk) {\n if (!bleSupported) {\n return {\n success: false,\n error:\n 'Web Bluetooth is not supported in this browser. Use Chrome or Edge.',\n printers: [],\n count: 0,\n };\n }\n\n console.log('[Web] Using BLE scan...');\n return await this._scanBlePrinters();\n }\n\n const subnet = options.subnet || this.defaultSubnet || '192.168.1';\n\n // Try TCP/IP scan first\n const result = await this._fetch(\n `/scan?subnet=${encodeURIComponent(subnet)}`\n );\n\n if (result.success && result.printers?.length > 0) {\n return {\n success: true,\n printers: (result.printers || []).map((p) => ({\n name: p.name || `Zebra @ ${p.ip}`,\n address: p.ip || p.address,\n type: 'network',\n paired: false,\n })),\n count: result.printers?.length || 0,\n method: 'tcp',\n };\n }\n\n // Fallback to BLE if TCP/IP found nothing and BLE is supported\n if (bleSupported) {\n console.log('[Web] TCP/IP found nothing, trying BLE...');\n const bleResult = await this._scanBlePrinters();\n\n if (bleResult.success) {\n return {\n ...bleResult,\n method: 'bluetooth',\n tcpFallback: true,\n };\n }\n }\n\n return {\n success: false,\n error: result.error || 'No printers found',\n printers: [],\n count: 0,\n bleAvailable: bleSupported,\n };\n }\n\n /**\n * Connect to a printer\n * @param {ConnectOptions} options - Connection parameters\n * @returns {Promise<ConnectResult>} Connection result\n */\n async connect(options) {\n const { address, type } = options || {};\n\n // BLE connection (like iOS)\n if (type === 'bluetooth') {\n if (!this._isBleSupported()) {\n return {\n success: false,\n connected: false,\n error:\n 'Web Bluetooth is not supported in this browser. Use Chrome or Edge.',\n };\n }\n\n console.log('[Web] Connecting via BLE...');\n return await this._connectBle(address);\n }\n\n // If no address provided but BLE devices were discovered, connect via BLE\n if (!address && this.discoveredBleDevices.length > 0) {\n console.log('[Web] No IP, using discovered BLE device...');\n return await this._connectBle(this.discoveredBleDevices[0].id);\n }\n\n if (!address) {\n // No address and no BLE devices - try to scan BLE\n if (this._isBleSupported()) {\n console.log('[Web] No address, scanning BLE...');\n const scanResult = await this._scanBlePrinters();\n if (scanResult.success && scanResult.printers.length > 0) {\n return await this._connectBle(scanResult.printers[0].address);\n }\n }\n\n return {\n success: false,\n connected: false,\n error: 'Printer address (IP) not provided',\n };\n }\n\n // TCP/IP connection via printer-server\n const result = await this._fetch('/connect', {\n method: 'POST',\n body: JSON.stringify({ ip: address }),\n });\n\n if (result.success) {\n this.connectedPrinter = {\n ip: address,\n name: result.printer?.name || `Zebra @ ${address}`,\n type: 'network',\n };\n\n // Save to localStorage for quick reconnect\n this._saveToStorage(this.connectedPrinter);\n\n return {\n success: true,\n connected: true,\n address,\n type: 'network',\n message: 'Connected to printer',\n };\n }\n\n // If TCP connection failed and BLE is supported, offer BLE as fallback\n if (this._isBleSupported()) {\n console.log('[Web] TCP failed, trying BLE...');\n return await this._connectBle(address);\n }\n\n return {\n success: false,\n connected: false,\n error: result.error || 'Connection failed',\n };\n }\n\n /**\n * Connect to printer by MAC address (web - redirect to IP)\n * @param {ConnectOptions} options - Connection parameters\n * @returns {Promise<ConnectResult>} Connection result\n */\n async connectByAddress(options) {\n console.warn('ZebraPrinter Web: connectByAddress - use IP address on web');\n return await this.connect(options);\n }\n\n /**\n * Check device by address\n * @param {ConnectOptions} options - Parameters with address\n * @returns {Promise<ConnectResult>} Reachability info\n */\n async checkDeviceByAddress(options) {\n const { address } = options || {};\n\n if (!address) {\n return {\n success: false,\n error: 'Address not provided',\n };\n }\n\n const result = await this._fetch('/check', {\n method: 'POST',\n body: JSON.stringify({ ip: address }),\n });\n\n return {\n success: true,\n reachable: result.reachable,\n address,\n };\n }\n\n /**\n * Disconnect from the printer\n * @param {{clearSaved?: boolean}} [options] - Options for disconnect\n * @returns {Promise<DisconnectResult>} Disconnect result\n */\n async disconnect(options = {}) {\n // Disconnect BLE if connected\n if (this.connectedPrinter?.type === 'bluetooth') {\n this._disconnectBle();\n }\n\n // Optionally clear saved printer\n if (options.clearSaved) {\n this._saveToStorage(null);\n }\n\n this.connectedPrinter = null;\n await Promise.resolve();\n return {\n success: true,\n connected: false,\n };\n }\n\n /**\n * Get device network info (web not supported directly)\n * @returns {Promise<{supported: boolean, type: string, error?: string, ip?: string, ssid?: string}>} Network info proxied via printer-server if available\n */\n async getNetworkInfo() {\n const serviceOk = await this._checkService();\n if (!serviceOk) {\n return {\n supported: false,\n type: 'unknown',\n error: 'Printer service not available for network info',\n };\n }\n\n const result = await this._fetch('/network-info', { method: 'GET' });\n\n if (result?.success) {\n return {\n supported: true,\n type: result.type || 'unknown',\n ip: result.ip,\n ssid: result.ssid,\n };\n }\n\n return {\n supported: false,\n type: 'unknown',\n error: result?.error || 'Network info not available',\n };\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // DEV HELPER METHODS\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Get saved printer from localStorage\n * @returns {Object|null} Saved printer settings\n */\n getSavedPrinter() {\n return this._loadFromStorage();\n }\n\n /**\n * Try to auto-connect to saved printer\n * @returns {Promise<ConnectResult>} Connection result\n */\n async autoConnect() {\n const savedPrinter = this._loadFromStorage();\n\n if (!savedPrinter) {\n return {\n success: false,\n connected: false,\n error: 'No saved printer found',\n };\n }\n\n console.log('[AutoConnect] Trying:', savedPrinter.name);\n\n // For BLE printers, need to re-scan and connect\n if (savedPrinter.type === 'bluetooth') {\n return await this.connect({\n type: 'bluetooth',\n address: savedPrinter.address,\n });\n }\n\n // For TCP/IP printers, check if reachable first\n if (savedPrinter.ip) {\n const check = await this._fetch('/check', {\n method: 'POST',\n body: JSON.stringify({ ip: savedPrinter.ip }),\n });\n\n if (check.reachable) {\n this.connectedPrinter = savedPrinter;\n return {\n success: true,\n connected: true,\n address: savedPrinter.ip,\n type: 'network',\n message: 'Auto-connected to saved printer',\n fromCache: true,\n };\n }\n }\n\n return {\n success: false,\n connected: false,\n error: 'Saved printer not reachable',\n };\n }\n\n /**\n * Show PWA-style printer picker dialog\n * Creates a native-feeling modal for printer selection\n * @param {{title?: string, scanOnOpen?: boolean}} [options] - Dialog options\n * @returns {Promise<{success: boolean, printer?: Object, cancelled?: boolean}>} Selected printer or cancellation\n */\n async showPrinterPicker(options = {}) {\n const { title = 'Select Printer', scanOnOpen = true } = options;\n\n const { promise, resolve } = Promise.withResolvers();\n\n // Create modal container\n const overlay = document.createElement('div');\n overlay.id = 'zebra-printer-picker';\n overlay.innerHTML = this._getPickerHTML(title);\n document.body.append(overlay);\n\n // Store state for this picker instance\n const pickerState = {\n printers: [],\n resolve,\n cleanup: () => overlay.remove(),\n };\n\n // Setup event handlers\n this._setupPickerEvents(overlay, pickerState);\n\n // Auto-scan if requested\n if (scanOnOpen) {\n await this._updatePickerPrinters(overlay, {});\n }\n\n return promise;\n }\n\n /**\n * Get picker HTML template\n * @private\n * @param {string} title - Dialog title\n * @returns {string} HTML string\n */\n _getPickerHTML(title) {\n const bleButton = this._isBleSupported()\n ? '<button class=\"zpp-btn zpp-btn-ble\" data-action=\"ble\">BLE</button>'\n : '';\n\n return `\n <style>\n #zebra-printer-picker {\n position: fixed;\n inset: 0;\n z-index: 999999;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n .zpp-modal {\n background: white;\n border-radius: 16px;\n width: 90%;\n max-width: 400px;\n max-height: 80vh;\n overflow: hidden;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\n animation: zpp-slideUp 0.3s ease-out;\n }\n @keyframes zpp-slideUp {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .zpp-header {\n padding: 20px;\n border-bottom: 1px solid #e5e7eb;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .zpp-header h2 { margin: 0; font-size: 18px; font-weight: 600; color: #111827; }\n .zpp-close { background: none; border: none; padding: 8px; cursor: pointer; border-radius: 8px; color: #6b7280; font-size: 18px; }\n .zpp-close:hover { background: #f3f4f6; }\n .zpp-content { padding: 16px; max-height: 400px; overflow-y: auto; }\n .zpp-loading { text-align: center; padding: 40px 20px; color: #6b7280; }\n .zpp-spinner {\n width: 40px; height: 40px;\n border: 3px solid #e5e7eb; border-top-color: #3b82f6;\n border-radius: 50%; animation: zpp-spin 1s linear infinite;\n margin: 0 auto 16px;\n }\n @keyframes zpp-spin { to { transform: rotate(360deg); } }\n .zpp-list { list-style: none; margin: 0; padding: 0; }\n .zpp-item {\n display: flex; align-items: center; padding: 16px;\n border: 1px solid #e5e7eb; border-radius: 12px;\n margin-bottom: 8px; cursor: pointer; transition: all 0.15s ease;\n }\n .zpp-item:hover { border-color: #3b82f6; background: #eff6ff; }\n .zpp-item.saved { border-color: #10b981; background: #ecfdf5; }\n .zpp-icon {\n width: 40px; height: 40px; background: #f3f4f6; border-radius: 10px;\n display: flex; align-items: center; justify-content: center;\n margin-right: 12px; font-size: 20px;\n }\n .zpp-info { flex: 1; }\n .zpp-name { font-weight: 500; color: #111827; margin-bottom: 2px; }\n .zpp-addr { font-size: 13px; color: #6b7280; }\n .zpp-badge { font-size: 11px; padding: 2px 8px; border-radius: 9999px; background: #10b981; color: white; margin-left: 8px; }\n .zpp-empty { text-align: center; padding: 40px 20px; color: #6b7280; }\n .zpp-actions { padding: 16px; border-top: 1px solid #e5e7eb; display: flex; gap: 8px; }\n .zpp-btn {\n flex: 1; padding: 12px 16px; border-radius: 10px;\n font-size: 14px; font-weight: 500; cursor: pointer;\n border: none; transition: all 0.15s ease;\n }\n .zpp-btn-secondary { background: #f3f4f6; color: #374151; }\n .zpp-btn-secondary:hover { background: #e5e7eb; }\n .zpp-btn-primary { background: #3b82f6; color: white; }\n .zpp-btn-primary:hover { background: #2563eb; }\n .zpp-btn-ble { background: #8b5cf6; color: white; }\n .zpp-btn-ble:hover { background: #7c3aed; }\n </style>\n <div class=\"zpp-modal\">\n <div class=\"zpp-header\">\n <h2>${title}</h2>\n <button class=\"zpp-close\" data-action=\"close\">✕</button>\n </div>\n <div class=\"zpp-content\">\n <div class=\"zpp-loading\">\n <div class=\"zpp-spinner\"></div>\n <div>Searching for printers...</div>\n </div>\n </div>\n <div class=\"zpp-actions\">\n <button class=\"zpp-btn zpp-btn-secondary\" data-action=\"cancel\">Cancel</button>\n <button class=\"zpp-btn zpp-btn-primary\" data-action=\"rescan\">Rescan</button>\n ${bleButton}\n </div>\n </div>\n `;\n }\n\n /**\n * Setup picker event handlers\n * @private\n * @param {HTMLElement} overlay - Picker overlay element\n * @param {Object} state - Picker state object\n */\n _setupPickerEvents(overlay, state) {\n // Handle button clicks\n overlay.addEventListener('click', async (e) => {\n const action = e.target.dataset?.action;\n\n if (action === 'close' || action === 'cancel') {\n state.cleanup();\n state.resolve({ success: false, cancelled: true });\n return;\n }\n\n if (action === 'rescan') {\n await this._updatePickerPrinters(overlay, { skipCache: true });\n return;\n }\n\n if (action === 'ble') {\n await this._updatePickerPrinters(overlay, { useBle: true });\n return;\n }\n\n // Check if clicked on printer item\n const item = e.target.closest('.zpp-item');\n if (item) {\n const printer = {\n address: item.dataset.address,\n type: item.dataset.type,\n name: item.dataset.name,\n };\n\n state.cleanup();\n\n // Auto-connect to selected printer\n const connectResult = await this.connect({\n address: printer.address,\n type: printer.type,\n });\n\n state.resolve({\n success: connectResult.success,\n printer: connectResult.success ? printer : undefined,\n error: connectResult.error,\n });\n }\n });\n\n // Close on overlay click (outside modal)\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) {\n state.cleanup();\n state.resolve({ success: false, cancelled: true });\n }\n });\n }\n\n /**\n * Update picker with scanned printers\n * @private\n * @param {HTMLElement} overlay - Picker overlay element\n * @param {Object} options - Scan options\n */\n async _updatePickerPrinters(overlay, options) {\n const content = overlay.querySelector('.zpp-content');\n const loadingMsg = options.useBle\n ? 'Scanning for Bluetooth printers...'\n : 'Searching for printers...';\n\n content.innerHTML = `\n <div class=\"zpp-loading\">\n <div class=\"zpp-spinner\"></div>\n <div>${loadingMsg}</div>\n </div>\n `;\n\n const result = await this.scanForPrinters(options);\n const printers = result.printers || [];\n\n if (printers.length === 0) {\n content.innerHTML = `\n <div class=\"zpp-empty\">\n <div style=\"font-size: 32px; margin-bottom: 16px; opacity: 0.5;\">⎙</div>\n <div>No printers found</div>\n <div style=\"font-size: 13px; margin-top: 8px;\">Make sure the printer is on and connected to the network</div>\n </div>\n `;\n return;\n }\n\n const savedPrinter = this._loadFromStorage();\n let html = '<ul class=\"zpp-list\">';\n\n for (const p of printers) {\n const isSaved =\n savedPrinter?.ip === p.address || savedPrinter?.address === p.address;\n const icon = p.type === 'bluetooth' ? 'BT' : 'IP';\n const typeLabel = p.type === 'bluetooth' ? 'Bluetooth' : 'Network';\n const badge = p.paired ? '<span class=\"zpp-badge\">Saved</span>' : '';\n\n html += `\n <li class=\"zpp-item ${isSaved ? 'saved' : ''}\" \n data-address=\"${p.address}\" \n data-type=\"${p.type}\" \n data-name=\"${p.name}\">\n <div class=\"zpp-icon\">${icon}</div>\n <div class=\"zpp-info\">\n <div class=\"zpp-name\">${p.name}${badge}</div>\n <div class=\"zpp-addr\">${p.address} • ${typeLabel}</div>\n </div>\n </li>\n `;\n }\n\n html += '</ul>';\n content.innerHTML = html;\n }\n}\n","import { registerPlugin } from '@capacitor/core';\n\nimport { ZebraPrinterWeb } from './web';\n\nconst ZebraPrinter = registerPlugin('ZebraPrinter', {\n web: () => new ZebraPrinterWeb(),\n // iOS and Android plugins will be auto-discovered via the Capacitor config\n});\n\nexport { ZebraPrinter };\n"],"names":[],"mappings":"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"}
|