@nitra/zebra 6.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/NitraZebra.podspec +16 -13
  2. package/Package.swift +28 -0
  3. package/README.md +72 -0
  4. package/android/build.gradle +16 -14
  5. package/android/src/main/AndroidManifest.xml +2 -22
  6. package/android/src/main/java/dev/nitra/zebra/Zebra.java +11 -0
  7. package/android/src/main/java/dev/nitra/zebra/ZebraPlugin.java +22 -0
  8. package/dist/plugin.js +842 -1296
  9. package/dist/plugin.js.map +1 -1
  10. package/ios/Sources/ZebraPlugin/Zebra.swift +8 -0
  11. package/ios/Sources/ZebraPlugin/ZebraPlugin.swift +23 -0
  12. package/ios/Tests/ZebraPluginTests/ZebraTests.swift +15 -0
  13. package/package.json +46 -54
  14. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  15. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  16. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  17. package/android/.gradle/8.9/gc.properties +0 -0
  18. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  19. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  20. package/android/.gradle/vcs-1/gc.properties +0 -0
  21. package/android/.project +0 -28
  22. package/android/capacitor.settings.gradle +0 -3
  23. package/android/gradle.properties +0 -19
  24. package/android/proguard-rules.pro +0 -21
  25. package/android/src/main/java/com/nitra/zebra_printer_plugin/ZebraPrinter.java +0 -1418
  26. package/android/src/main/res/xml/capacitor_plugins.xml +0 -4
  27. package/android/variables.gradle +0 -14
  28. package/ios/Info.plist +0 -36
  29. package/ios/Plugin/ZebraPrinterPlugin.m +0 -16
  30. package/ios/Plugin/ZebraPrinterPlugin.swift +0 -465
  31. /package/android/{.gradle/8.9/dependencies-accessors/gc.properties → src/main/res/.gitkeep} +0 -0
package/dist/plugin.js CHANGED
@@ -1,1297 +1,843 @@
1
- import { WebPlugin, registerPlugin } from '@capacitor/core';
2
-
3
- /**
4
- * ZebraPrinterWeb - Web implementation for development
5
- *
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
- *
9
- * Production (iOS) uses the native BLE implementation.
10
- */
11
- /**
12
- * @typedef {import('../definitions').EchoOptions} EchoOptions
13
- * @typedef {import('../definitions').EchoResult} EchoResult
14
- * @typedef {import('../definitions').PrintTextOptions} PrintTextOptions
15
- * @typedef {import('../definitions').PrintResult} PrintResult
16
- * @typedef {import('../definitions').PrinterStatus} PrinterStatus
17
- * @typedef {import('../definitions').PrinterStatusResult} PrinterStatusResult
18
- * @typedef {import('../definitions').ScanResult} ScanResult
19
- * @typedef {import('../definitions').ConnectOptions} ConnectOptions
20
- * @typedef {import('../definitions').ConnectResult} ConnectResult
21
- * @typedef {import('../definitions').DisconnectResult} DisconnectResult
22
- * @typedef {import('../definitions').PermissionResult} PermissionResult
23
- */
24
-
25
- const API_BASE = '/api/printer';
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
-
36
- class ZebraPrinterWeb extends WebPlugin {
37
- constructor() {
38
- super();
39
- this.serviceAvailable = null;
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;
124
- }
125
-
126
- /**
127
- * Check if printer-server is reachable
128
- * @returns {Promise<boolean>} True if reachable
129
- */
130
- async _checkService() {
131
- if (this.serviceAvailable !== null) {
132
- return this.serviceAvailable;
133
- }
134
-
135
- try {
136
- const response = await fetch(`${API_BASE}/status`, {
137
- method: 'GET',
138
- headers: { 'Content-Type': 'application/json' },
139
- });
140
- const data = await response.json();
141
- this.serviceAvailable = data.success === true;
142
- return this.serviceAvailable;
143
- } catch (error) {
144
- console.warn('ZebraPrinter: service check failed', error);
145
- this.serviceAvailable = false;
146
- return false;
147
- }
148
- }
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
-
404
- /**
405
- * Fetch wrapper with error handling
406
- * @param {string} endpoint - API route
407
- * @param {RequestInit} [options] - Fetch configuration
408
- * @returns {Promise<any>} Response JSON
409
- */
410
- async _fetch(endpoint, options = {}) {
411
- try {
412
- const response = await fetch(`${API_BASE}${endpoint}`, {
413
- headers: { 'Content-Type': 'application/json' },
414
- ...options,
415
- });
416
- return await response.json();
417
- } catch (error) {
418
- console.warn(`ZebraPrinter Web: ${endpoint} failed -`, error.message);
419
- return {
420
- success: false,
421
- error:
422
- 'Printer service is not available. Ensure Vite dev server runs with printer-server plugin.',
423
- serviceUnavailable: true,
424
- };
425
- }
426
- }
427
-
428
- /**
429
- * Derive default subnet from host IP, if possible
430
- * @returns {string} Example: "192.168.1" or "10.0.0"
431
- */
432
- _getDefaultSubnet() {
433
- if (typeof window === 'undefined') return '192.168.1';
434
- const host = window.location.hostname;
435
- const ipMatch = host.match(
436
- /^(10|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\.(\d+)\.(\d+)$/
437
- );
438
- if (ipMatch) {
439
- return `${ipMatch[1]}.${ipMatch[3]}`;
440
- }
441
- return '192.168.1';
442
- }
443
-
444
- /**
445
- * Wrap plain text into a basic ZPL label unless it already looks like ZPL
446
- * @param {string} text - Input text or ZPL
447
- * @returns {string} ZPL payload
448
- */
449
- _wrapTextIfNeeded(text) {
450
- const isZplLike =
451
- typeof text === 'string' &&
452
- /\^XA/i.test(text) &&
453
- (/\^XZ/i.test(text) || /\^JUS/i.test(text));
454
- if (isZplLike) {
455
- return text;
456
- }
457
-
458
- const safeText = String(text ?? '').replaceAll(/\r?\n/g, String.raw`\&`);
459
- return [
460
- '^XA',
461
- '^CI28',
462
- '^FO50,50',
463
- '^A0N,30,30',
464
- `^FD${safeText}^FS`,
465
- '^XZ',
466
- ].join('\n');
467
- }
468
-
469
- /**
470
- * Echo a value
471
- * @param {EchoOptions} options - Input data
472
- * @returns {Promise<EchoResult & {platform: string, serviceAvailable: boolean}>} Echo result with platform info
473
- */
474
- async echo(options) {
475
- console.log('ZebraPrinter: echo called with options:', options);
476
- const serviceOk = await this._checkService();
477
- return {
478
- value: options.value,
479
- platform: 'web',
480
- serviceAvailable: serviceOk,
481
- };
482
- }
483
-
484
- /**
485
- * Check if required permissions are granted
486
- * @returns {Promise<PermissionResult>} Permission status
487
- */
488
- async checkPermissions() {
489
- const serviceOk = await this._checkService();
490
- const bleSupported = this._isBleSupported();
491
-
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
- );
499
- return {
500
- hasPermissions: false,
501
- missingPermissions: ['printer-server', 'bluetooth'],
502
- bluetoothSupported: false,
503
- bleSupported: false,
504
- webMode: true,
505
- serviceAvailable: false,
506
- };
507
- }
508
-
509
- return {
510
- hasPermissions: true,
511
- missingPermissions: [],
512
- bluetoothSupported: bleSupported,
513
- bleSupported: bleSupported,
514
- webMode: true,
515
- serviceAvailable: serviceOk,
516
- // New: indicate which methods are available
517
- tcpAvailable: serviceOk,
518
- bleAvailable: bleSupported,
519
- };
520
- }
521
-
522
- /**
523
- * Request required permissions
524
- * @returns {Promise<PermissionResult>} Permission status
525
- */
526
- async requestPermissions() {
527
- // Not needed on web - just check service
528
- return await this.checkPermissions();
529
- }
530
-
531
- /**
532
- * Print text to the connected printer
533
- * @param {PrintTextOptions} options - Parameters with ZPL text
534
- * @returns {Promise<PrintResult>} Print result
535
- */
536
- async printText(options) {
537
- const { text } = options || {};
538
-
539
- if (!text) {
540
- return {
541
- success: false,
542
- message: 'No text provided for printing',
543
- error: 'Missing text parameter',
544
- };
545
- }
546
-
547
- const zplPayload = this._wrapTextIfNeeded(text);
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
572
- const result = await this._fetch('/print', {
573
- method: 'POST',
574
- body: JSON.stringify({
575
- zpl: zplPayload,
576
- ip: this.connectedPrinter?.ip,
577
- }),
578
- });
579
-
580
- if (result.success) {
581
- return {
582
- success: true,
583
- message: 'Print successful',
584
- zpl: `${zplPayload.slice(0, 100)}...`,
585
- bytes: zplPayload.length,
586
- method: 'tcp',
587
- };
588
- }
589
-
590
- return {
591
- success: false,
592
- message: result.error || 'Print failed',
593
- error: result.error,
594
- };
595
- }
596
-
597
- /**
598
- * Get printer status
599
- * @returns {Promise<PrinterStatus>} Printer status
600
- */
601
- async getStatus() {
602
- if (!this.connectedPrinter) {
603
- return {
604
- connected: false,
605
- status: 'disconnected',
606
- };
607
- }
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
623
- const result = await this._fetch('/check', {
624
- method: 'POST',
625
- body: JSON.stringify({ ip: this.connectedPrinter.ip }),
626
- });
627
-
628
- return {
629
- connected: result.reachable === true,
630
- status: result.reachable ? 'connected' : 'disconnected',
631
- printerAddress: this.connectedPrinter.ip,
632
- printerType: 'network',
633
- };
634
- }
635
-
636
- /**
637
- * Check printer status with ZPL command
638
- * @returns {Promise<PrinterStatusResult>} Status request result
639
- */
640
- async checkPrinterStatus() {
641
- if (!this.connectedPrinter) {
642
- return {
643
- success: false,
644
- message: 'No printer connected',
645
- error: 'Not connected',
646
- };
647
- }
648
-
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
665
- const result = await this._fetch('/print', {
666
- method: 'POST',
667
- body: JSON.stringify({
668
- zpl: statusCommand,
669
- ip: this.connectedPrinter.ip,
670
- }),
671
- });
672
-
673
- return {
674
- success: result.success,
675
- message: result.success ? 'Status command sent' : result.error,
676
- command: statusCommand,
677
- method: 'tcp',
678
- };
679
- }
680
-
681
- /**
682
- * Scan for available printers
683
- * @param {{subnet?: string, useBle?: boolean, skipCache?: boolean}} [options] - Custom subnet or BLE mode
684
- * @returns {Promise<ScanResult>} List of found printers
685
- */
686
- async scanForPrinters(options = {}) {
687
- const { useBle, skipCache } = options;
688
- const serviceOk = await this._checkService();
689
- const bleSupported = this._isBleSupported();
690
-
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();
738
- }
739
-
740
- const subnet = options.subnet || this.defaultSubnet || '192.168.1';
741
-
742
- // Try TCP/IP scan first
743
- const result = await this._fetch(
744
- `/scan?subnet=${encodeURIComponent(subnet)}`
745
- );
746
-
747
- if (result.success && result.printers?.length > 0) {
748
- return {
749
- success: true,
750
- printers: (result.printers || []).map((p) => ({
751
- name: p.name || `Zebra @ ${p.ip}`,
752
- address: p.ip || p.address,
753
- type: 'network',
754
- paired: false,
755
- })),
756
- count: result.printers?.length || 0,
757
- method: 'tcp',
758
- };
759
- }
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
-
775
- return {
776
- success: false,
777
- error: result.error || 'No printers found',
778
- printers: [],
779
- count: 0,
780
- bleAvailable: bleSupported,
781
- };
782
- }
783
-
784
- /**
785
- * Connect to a printer
786
- * @param {ConnectOptions} options - Connection parameters
787
- * @returns {Promise<ConnectResult>} Connection result
788
- */
789
- async connect(options) {
790
- const { address, type } = options || {};
791
-
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);
805
- }
806
-
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
-
823
- return {
824
- success: false,
825
- connected: false,
826
- error: 'Printer address (IP) not provided',
827
- };
828
- }
829
-
830
- // TCP/IP connection via printer-server
831
- const result = await this._fetch('/connect', {
832
- method: 'POST',
833
- body: JSON.stringify({ ip: address }),
834
- });
835
-
836
- if (result.success) {
837
- this.connectedPrinter = {
838
- ip: address,
839
- name: result.printer?.name || `Zebra @ ${address}`,
840
- type: 'network',
841
- };
842
-
843
- // Save to localStorage for quick reconnect
844
- this._saveToStorage(this.connectedPrinter);
845
-
846
- return {
847
- success: true,
848
- connected: true,
849
- address,
850
- type: 'network',
851
- message: 'Connected to printer',
852
- };
853
- }
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
-
861
- return {
862
- success: false,
863
- connected: false,
864
- error: result.error || 'Connection failed',
865
- };
866
- }
867
-
868
- /**
869
- * Connect to printer by MAC address (web - redirect to IP)
870
- * @param {ConnectOptions} options - Connection parameters
871
- * @returns {Promise<ConnectResult>} Connection result
872
- */
873
- async connectByAddress(options) {
874
- console.warn('ZebraPrinter Web: connectByAddress - use IP address on web');
875
- return await this.connect(options);
876
- }
877
-
878
- /**
879
- * Check device by address
880
- * @param {ConnectOptions} options - Parameters with address
881
- * @returns {Promise<ConnectResult>} Reachability info
882
- */
883
- async checkDeviceByAddress(options) {
884
- const { address } = options || {};
885
-
886
- if (!address) {
887
- return {
888
- success: false,
889
- error: 'Address not provided',
890
- };
891
- }
892
-
893
- const result = await this._fetch('/check', {
894
- method: 'POST',
895
- body: JSON.stringify({ ip: address }),
896
- });
897
-
898
- return {
899
- success: true,
900
- reachable: result.reachable,
901
- address,
902
- };
903
- }
904
-
905
- /**
906
- * Disconnect from the printer
907
- * @param {{clearSaved?: boolean}} [options] - Options for disconnect
908
- * @returns {Promise<DisconnectResult>} Disconnect result
909
- */
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
-
921
- this.connectedPrinter = null;
922
- await Promise.resolve();
923
- return {
924
- success: true,
925
- connected: false,
926
- };
927
- }
928
-
929
- /**
930
- * Get device network info (web not supported directly)
931
- * @returns {Promise<{supported: boolean, type: string, error?: string, ip?: string, ssid?: string}>} Network info proxied via printer-server if available
932
- */
933
- async getNetworkInfo() {
934
- const serviceOk = await this._checkService();
935
- if (!serviceOk) {
936
- return {
937
- supported: false,
938
- type: 'unknown',
939
- error: 'Printer service not available for network info',
940
- };
941
- }
942
-
943
- const result = await this._fetch('/network-info', { method: 'GET' });
944
-
945
- if (result?.success) {
946
- return {
947
- supported: true,
948
- type: result.type || 'unknown',
949
- ip: result.ip,
950
- ssid: result.ssid,
951
- };
952
- }
953
-
954
- return {
955
- supported: false,
956
- type: 'unknown',
957
- error: result?.error || 'Network info not available',
958
- };
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
- }
1
+ import { WebPlugin, registerPlugin } from "@capacitor/core";
2
+
3
+ //#region \0rolldown/runtime.js
4
+ var __defProp = Object.defineProperty;
5
+ var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) {
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ }
14
+ if (!no_symbols) {
15
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ }
17
+ return target;
18
+ };
19
+
20
+ //#endregion
21
+ //#region ../node_modules/.bun/consola@3.4.2/node_modules/consola/dist/core.mjs
22
+ function isPlainObject$1(value) {
23
+ if (value === null || typeof value !== "object") return false;
24
+ const prototype = Object.getPrototypeOf(value);
25
+ if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) return false;
26
+ if (Symbol.iterator in value) return false;
27
+ if (Symbol.toStringTag in value) return Object.prototype.toString.call(value) === "[object Module]";
28
+ return true;
1289
29
  }
1290
-
1291
- const ZebraPrinter = registerPlugin('ZebraPrinter', {
1292
- web: () => new ZebraPrinterWeb(),
1293
- // iOS and Android plugins will be auto-discovered via the Capacitor config
1294
- });
1295
-
1296
- export { ZebraPrinter };
1297
- //# sourceMappingURL=plugin.js.map
30
+ function _defu(baseObject, defaults, namespace = ".", merger) {
31
+ if (!isPlainObject$1(defaults)) return _defu(baseObject, {}, namespace, merger);
32
+ const object = Object.assign({}, defaults);
33
+ for (const key in baseObject) {
34
+ if (key === "__proto__" || key === "constructor") continue;
35
+ const value = baseObject[key];
36
+ if (value === null || value === void 0) continue;
37
+ if (merger && merger(object, key, value, namespace)) continue;
38
+ if (Array.isArray(value) && Array.isArray(object[key])) object[key] = [...value, ...object[key]];
39
+ else if (isPlainObject$1(value) && isPlainObject$1(object[key])) object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger);
40
+ else object[key] = value;
41
+ }
42
+ return object;
43
+ }
44
+ function createDefu(merger) {
45
+ return (...arguments_) => arguments_.reduce((p, c) => _defu(p, c, "", merger), {});
46
+ }
47
+ function isPlainObject(obj) {
48
+ return Object.prototype.toString.call(obj) === "[object Object]";
49
+ }
50
+ function isLogObj(arg) {
51
+ if (!isPlainObject(arg)) return false;
52
+ if (!arg.message && !arg.args) return false;
53
+ if (arg.stack) return false;
54
+ return true;
55
+ }
56
+ function _normalizeLogLevel(input, types = {}, defaultLevel = 3) {
57
+ if (input === void 0) return defaultLevel;
58
+ if (typeof input === "number") return input;
59
+ if (types[input] && types[input].level !== void 0) return types[input].level;
60
+ return defaultLevel;
61
+ }
62
+ function createConsola$1(options = {}) {
63
+ return new Consola(options);
64
+ }
65
+ var LogLevels, LogTypes, defu, paused, queue, Consola;
66
+ var init_core = __esmMin((() => {
67
+ LogLevels = {
68
+ silent: Number.NEGATIVE_INFINITY,
69
+ fatal: 0,
70
+ error: 0,
71
+ warn: 1,
72
+ log: 2,
73
+ info: 3,
74
+ success: 3,
75
+ fail: 3,
76
+ ready: 3,
77
+ start: 3,
78
+ box: 3,
79
+ debug: 4,
80
+ trace: 5,
81
+ verbose: Number.POSITIVE_INFINITY
82
+ };
83
+ LogTypes = {
84
+ silent: { level: -1 },
85
+ fatal: { level: LogLevels.fatal },
86
+ error: { level: LogLevels.error },
87
+ warn: { level: LogLevels.warn },
88
+ log: { level: LogLevels.log },
89
+ info: { level: LogLevels.info },
90
+ success: { level: LogLevels.success },
91
+ fail: { level: LogLevels.fail },
92
+ ready: { level: LogLevels.info },
93
+ start: { level: LogLevels.info },
94
+ box: { level: LogLevels.info },
95
+ debug: { level: LogLevels.debug },
96
+ trace: { level: LogLevels.trace },
97
+ verbose: { level: LogLevels.verbose }
98
+ };
99
+ defu = createDefu();
100
+ paused = false;
101
+ queue = [];
102
+ Consola = class Consola {
103
+ options;
104
+ _lastLog;
105
+ _mockFn;
106
+ /**
107
+ * Creates an instance of Consola with specified options or defaults.
108
+ *
109
+ * @param {Partial<ConsolaOptions>} [options={}] - Configuration options for the Consola instance.
110
+ */
111
+ constructor(options = {}) {
112
+ const types = options.types || LogTypes;
113
+ this.options = defu({
114
+ ...options,
115
+ defaults: { ...options.defaults },
116
+ level: _normalizeLogLevel(options.level, types),
117
+ reporters: [...options.reporters || []]
118
+ }, {
119
+ types: LogTypes,
120
+ throttle: 1e3,
121
+ throttleMin: 5,
122
+ formatOptions: {
123
+ date: true,
124
+ colors: false,
125
+ compact: true
126
+ }
127
+ });
128
+ for (const type in types) {
129
+ const defaults = {
130
+ type,
131
+ ...this.options.defaults,
132
+ ...types[type]
133
+ };
134
+ this[type] = this._wrapLogFn(defaults);
135
+ this[type].raw = this._wrapLogFn(defaults, true);
136
+ }
137
+ if (this.options.mockFn) this.mockTypes();
138
+ this._lastLog = {};
139
+ }
140
+ /**
141
+ * Gets the current log level of the Consola instance.
142
+ *
143
+ * @returns {number} The current log level.
144
+ */
145
+ get level() {
146
+ return this.options.level;
147
+ }
148
+ /**
149
+ * Sets the minimum log level that will be output by the instance.
150
+ *
151
+ * @param {number} level - The new log level to set.
152
+ */
153
+ set level(level) {
154
+ this.options.level = _normalizeLogLevel(level, this.options.types, this.options.level);
155
+ }
156
+ /**
157
+ * Displays a prompt to the user and returns the response.
158
+ * Throw an error if `prompt` is not supported by the current configuration.
159
+ *
160
+ * @template T
161
+ * @param {string} message - The message to display in the prompt.
162
+ * @param {T} [opts] - Optional options for the prompt. See {@link PromptOptions}.
163
+ * @returns {promise<T>} A promise that infer with the prompt options. See {@link PromptOptions}.
164
+ */
165
+ prompt(message, opts) {
166
+ if (!this.options.prompt) throw new Error("prompt is not supported!");
167
+ return this.options.prompt(message, opts);
168
+ }
169
+ /**
170
+ * Creates a new instance of Consola, inheriting options from the current instance, with possible overrides.
171
+ *
172
+ * @param {Partial<ConsolaOptions>} options - Optional overrides for the new instance. See {@link ConsolaOptions}.
173
+ * @returns {ConsolaInstance} A new Consola instance. See {@link ConsolaInstance}.
174
+ */
175
+ create(options) {
176
+ const instance = new Consola({
177
+ ...this.options,
178
+ ...options
179
+ });
180
+ if (this._mockFn) instance.mockTypes(this._mockFn);
181
+ return instance;
182
+ }
183
+ /**
184
+ * Creates a new Consola instance with the specified default log object properties.
185
+ *
186
+ * @param {InputLogObject} defaults - Default properties to include in any log from the new instance. See {@link InputLogObject}.
187
+ * @returns {ConsolaInstance} A new Consola instance. See {@link ConsolaInstance}.
188
+ */
189
+ withDefaults(defaults) {
190
+ return this.create({
191
+ ...this.options,
192
+ defaults: {
193
+ ...this.options.defaults,
194
+ ...defaults
195
+ }
196
+ });
197
+ }
198
+ /**
199
+ * Creates a new Consola instance with a specified tag, which will be included in every log.
200
+ *
201
+ * @param {string} tag - The tag to include in each log of the new instance.
202
+ * @returns {ConsolaInstance} A new Consola instance. See {@link ConsolaInstance}.
203
+ */
204
+ withTag(tag) {
205
+ return this.withDefaults({ tag: this.options.defaults.tag ? this.options.defaults.tag + ":" + tag : tag });
206
+ }
207
+ /**
208
+ * Adds a custom reporter to the Consola instance.
209
+ * Reporters will be called for each log message, depending on their implementation and log level.
210
+ *
211
+ * @param {ConsolaReporter} reporter - The reporter to add. See {@link ConsolaReporter}.
212
+ * @returns {Consola} The current Consola instance.
213
+ */
214
+ addReporter(reporter) {
215
+ this.options.reporters.push(reporter);
216
+ return this;
217
+ }
218
+ /**
219
+ * Removes a custom reporter from the Consola instance.
220
+ * If no reporter is specified, all reporters will be removed.
221
+ *
222
+ * @param {ConsolaReporter} reporter - The reporter to remove. See {@link ConsolaReporter}.
223
+ * @returns {Consola} The current Consola instance.
224
+ */
225
+ removeReporter(reporter) {
226
+ if (reporter) {
227
+ const i = this.options.reporters.indexOf(reporter);
228
+ if (i !== -1) return this.options.reporters.splice(i, 1);
229
+ } else this.options.reporters.splice(0);
230
+ return this;
231
+ }
232
+ /**
233
+ * Replaces all reporters of the Consola instance with the specified array of reporters.
234
+ *
235
+ * @param {ConsolaReporter[]} reporters - The new reporters to set. See {@link ConsolaReporter}.
236
+ * @returns {Consola} The current Consola instance.
237
+ */
238
+ setReporters(reporters) {
239
+ this.options.reporters = Array.isArray(reporters) ? reporters : [reporters];
240
+ return this;
241
+ }
242
+ wrapAll() {
243
+ this.wrapConsole();
244
+ this.wrapStd();
245
+ }
246
+ restoreAll() {
247
+ this.restoreConsole();
248
+ this.restoreStd();
249
+ }
250
+ /**
251
+ * Overrides console methods with Consola logging methods for consistent logging.
252
+ */
253
+ wrapConsole() {
254
+ for (const type in this.options.types) {
255
+ if (!console["__" + type]) console["__" + type] = console[type];
256
+ console[type] = this[type].raw;
257
+ }
258
+ }
259
+ /**
260
+ * Restores the original console methods, removing Consola overrides.
261
+ */
262
+ restoreConsole() {
263
+ for (const type in this.options.types) if (console["__" + type]) {
264
+ console[type] = console["__" + type];
265
+ delete console["__" + type];
266
+ }
267
+ }
268
+ /**
269
+ * Overrides standard output and error streams to redirect them through Consola.
270
+ */
271
+ wrapStd() {
272
+ this._wrapStream(this.options.stdout, "log");
273
+ this._wrapStream(this.options.stderr, "log");
274
+ }
275
+ _wrapStream(stream, type) {
276
+ if (!stream) return;
277
+ if (!stream.__write) stream.__write = stream.write;
278
+ stream.write = (data) => {
279
+ this[type].raw(String(data).trim());
280
+ };
281
+ }
282
+ /**
283
+ * Restores the original standard output and error streams, removing the Consola redirection.
284
+ */
285
+ restoreStd() {
286
+ this._restoreStream(this.options.stdout);
287
+ this._restoreStream(this.options.stderr);
288
+ }
289
+ _restoreStream(stream) {
290
+ if (!stream) return;
291
+ if (stream.__write) {
292
+ stream.write = stream.__write;
293
+ delete stream.__write;
294
+ }
295
+ }
296
+ /**
297
+ * Pauses logging, queues incoming logs until resumed.
298
+ */
299
+ pauseLogs() {
300
+ paused = true;
301
+ }
302
+ /**
303
+ * Resumes logging, processing any queued logs.
304
+ */
305
+ resumeLogs() {
306
+ paused = false;
307
+ const _queue = queue.splice(0);
308
+ for (const item of _queue) item[0]._logFn(item[1], item[2]);
309
+ }
310
+ /**
311
+ * Replaces logging methods with mocks if a mock function is provided.
312
+ *
313
+ * @param {ConsolaOptions["mockFn"]} mockFn - The function to use for mocking logging methods. See {@link ConsolaOptions["mockFn"]}.
314
+ */
315
+ mockTypes(mockFn) {
316
+ const _mockFn = mockFn || this.options.mockFn;
317
+ this._mockFn = _mockFn;
318
+ if (typeof _mockFn !== "function") return;
319
+ for (const type in this.options.types) {
320
+ this[type] = _mockFn(type, this.options.types[type]) || this[type];
321
+ this[type].raw = this[type];
322
+ }
323
+ }
324
+ _wrapLogFn(defaults, isRaw) {
325
+ return (...args) => {
326
+ if (paused) {
327
+ queue.push([
328
+ this,
329
+ defaults,
330
+ args,
331
+ isRaw
332
+ ]);
333
+ return;
334
+ }
335
+ return this._logFn(defaults, args, isRaw);
336
+ };
337
+ }
338
+ _logFn(defaults, args, isRaw) {
339
+ if ((defaults.level || 0) > this.level) return false;
340
+ const logObj = {
341
+ date: /* @__PURE__ */ new Date(),
342
+ args: [],
343
+ ...defaults,
344
+ level: _normalizeLogLevel(defaults.level, this.options.types)
345
+ };
346
+ if (!isRaw && args.length === 1 && isLogObj(args[0])) Object.assign(logObj, args[0]);
347
+ else logObj.args = [...args];
348
+ if (logObj.message) {
349
+ logObj.args.unshift(logObj.message);
350
+ delete logObj.message;
351
+ }
352
+ if (logObj.additional) {
353
+ if (!Array.isArray(logObj.additional)) logObj.additional = logObj.additional.split("\n");
354
+ logObj.args.push("\n" + logObj.additional.join("\n"));
355
+ delete logObj.additional;
356
+ }
357
+ logObj.type = typeof logObj.type === "string" ? logObj.type.toLowerCase() : "log";
358
+ logObj.tag = typeof logObj.tag === "string" ? logObj.tag : "";
359
+ const resolveLog = (newLog = false) => {
360
+ const repeated = (this._lastLog.count || 0) - this.options.throttleMin;
361
+ if (this._lastLog.object && repeated > 0) {
362
+ const args2 = [...this._lastLog.object.args];
363
+ if (repeated > 1) args2.push(`(repeated ${repeated} times)`);
364
+ this._log({
365
+ ...this._lastLog.object,
366
+ args: args2
367
+ });
368
+ this._lastLog.count = 1;
369
+ }
370
+ if (newLog) {
371
+ this._lastLog.object = logObj;
372
+ this._log(logObj);
373
+ }
374
+ };
375
+ clearTimeout(this._lastLog.timeout);
376
+ const diffTime = this._lastLog.time && logObj.date ? logObj.date.getTime() - this._lastLog.time.getTime() : 0;
377
+ this._lastLog.time = logObj.date;
378
+ if (diffTime < this.options.throttle) try {
379
+ const serializedLog = JSON.stringify([
380
+ logObj.type,
381
+ logObj.tag,
382
+ logObj.args
383
+ ]);
384
+ const isSameLog = this._lastLog.serialized === serializedLog;
385
+ this._lastLog.serialized = serializedLog;
386
+ if (isSameLog) {
387
+ this._lastLog.count = (this._lastLog.count || 0) + 1;
388
+ if (this._lastLog.count > this.options.throttleMin) {
389
+ this._lastLog.timeout = setTimeout(resolveLog, this.options.throttle);
390
+ return;
391
+ }
392
+ }
393
+ } catch {}
394
+ resolveLog(true);
395
+ }
396
+ _log(logObj) {
397
+ for (const reporter of this.options.reporters) reporter.log(logObj, { options: this.options });
398
+ }
399
+ };
400
+ Consola.prototype.add = Consola.prototype.addReporter;
401
+ Consola.prototype.remove = Consola.prototype.removeReporter;
402
+ Consola.prototype.clear = Consola.prototype.removeReporter;
403
+ Consola.prototype.withScope = Consola.prototype.withTag;
404
+ Consola.prototype.mock = Consola.prototype.mockTypes;
405
+ Consola.prototype.pause = Consola.prototype.pauseLogs;
406
+ Consola.prototype.resume = Consola.prototype.resumeLogs;
407
+ }));
408
+
409
+ //#endregion
410
+ //#region ../node_modules/.bun/consola@3.4.2/node_modules/consola/dist/browser.mjs
411
+ function createConsola(options = {}) {
412
+ return createConsola$1({
413
+ reporters: options.reporters || [new BrowserReporter({})],
414
+ prompt(message, options2 = {}) {
415
+ if (options2.type === "confirm") return Promise.resolve(confirm(message));
416
+ return Promise.resolve(prompt(message));
417
+ },
418
+ ...options
419
+ });
420
+ }
421
+ var BrowserReporter, consola;
422
+ var init_browser$1 = __esmMin((() => {
423
+ init_core();
424
+ BrowserReporter = class {
425
+ options;
426
+ defaultColor;
427
+ levelColorMap;
428
+ typeColorMap;
429
+ constructor(options) {
430
+ this.options = { ...options };
431
+ this.defaultColor = "#7f8c8d";
432
+ this.levelColorMap = {
433
+ 0: "#c0392b",
434
+ 1: "#f39c12",
435
+ 3: "#00BCD4"
436
+ };
437
+ this.typeColorMap = { success: "#2ecc71" };
438
+ }
439
+ _getLogFn(level) {
440
+ if (level < 1) return console.__error || console.error;
441
+ if (level === 1) return console.__warn || console.warn;
442
+ return console.__log || console.log;
443
+ }
444
+ log(logObj) {
445
+ const consoleLogFn = this._getLogFn(logObj.level);
446
+ const type = logObj.type === "log" ? "" : logObj.type;
447
+ const tag = logObj.tag || "";
448
+ const style = `
449
+ background: ${this.typeColorMap[logObj.type] || this.levelColorMap[logObj.level] || this.defaultColor};
450
+ border-radius: 0.5em;
451
+ color: white;
452
+ font-weight: bold;
453
+ padding: 2px 0.5em;
454
+ `;
455
+ const badge = `%c${[tag, type].filter(Boolean).join(":")}`;
456
+ if (typeof logObj.args[0] === "string") consoleLogFn(`${badge}%c ${logObj.args[0]}`, style, "", ...logObj.args.slice(1));
457
+ else consoleLogFn(badge, style, ...logObj.args);
458
+ }
459
+ };
460
+ consola = createConsola();
461
+ }));
462
+
463
+ //#endregion
464
+ //#region ../node_modules/.bun/@nitra+consola@2.3.0/node_modules/@nitra/consola/src/browser.js
465
+ var options, CanvasPopupReporter, isPopupDebugEnabled, canvasPopupReporter, defaultConsola;
466
+ var init_browser = __esmMin((() => {
467
+ init_browser$1();
468
+ options = {};
469
+ if (typeof __CONSOLA_LEVEL_DEBUG__ !== "undefined") options.level = 4;
470
+ else if (typeof location !== "undefined" && location.protocol === "http:") options.level = 4;
471
+ else if (typeof process !== "undefined" && (process.env.DEV || process.env.CONSOLA_LEVEL_DEBUG)) options.level = 4;
472
+ CanvasPopupReporter = class CanvasPopupReporter {
473
+ static PADDING = 12;
474
+ static LABEL_PADDING = 8;
475
+ static LINE_HEIGHT = 20;
476
+ static BUTTON_SIZE = 18;
477
+ static BUTTON_PADDING = 6;
478
+ static MIN_HEIGHT = 36;
479
+ static MAX_MESSAGE_LENGTH = 500;
480
+ static FONT_TEXT = "13px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
481
+ static FONT_MONO = "11px monospace";
482
+ constructor() {
483
+ this.canvas = null;
484
+ this.ctx = null;
485
+ this.messages = [];
486
+ this.messageId = 0;
487
+ this.initCanvas();
488
+ }
489
+ initCanvas() {
490
+ if (typeof document === "undefined") return;
491
+ const init = () => {
492
+ if (!document.body) {
493
+ setTimeout(init, 10);
494
+ return;
495
+ }
496
+ let canvas = document.querySelector("#consola-popup-canvas");
497
+ if (!canvas) {
498
+ canvas = document.createElement("canvas");
499
+ canvas.id = "consola-popup-canvas";
500
+ canvas.style.position = "fixed";
501
+ canvas.style.top = "0";
502
+ canvas.style.left = "0";
503
+ canvas.style.width = "100%";
504
+ canvas.style.height = "100%";
505
+ canvas.style.pointerEvents = "auto";
506
+ canvas.style.zIndex = "999999";
507
+ document.body.append(canvas);
508
+ canvas.addEventListener("click", (e) => this.handleClick(e));
509
+ }
510
+ this.canvas = canvas;
511
+ this.resizeCanvas();
512
+ window.addEventListener("resize", () => this.resizeCanvas());
513
+ this.animate();
514
+ };
515
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
516
+ else init();
517
+ }
518
+ handleClick(event) {
519
+ if (!this.canvas || !this.ctx) return;
520
+ const rect = this.canvas.getBoundingClientRect();
521
+ const x = event.clientX - rect.left;
522
+ const y = event.clientY - rect.top;
523
+ for (let i = this.messages.length - 1; i >= 0; i--) {
524
+ const msg = this.messages[i];
525
+ if (msg.fileInfo && msg.fileLinkX && msg.fileLinkY && msg.fileLinkWidth) {
526
+ if (x >= msg.fileLinkX - msg.fileLinkWidth && x <= msg.fileLinkX && y >= msg.fileLinkY && y <= msg.fileLinkY + 14) {
527
+ console.log(`%c${msg.fileInfo.file}:${msg.fileInfo.line}`, "color: #1976D2; text-decoration: underline;");
528
+ return;
529
+ }
530
+ }
531
+ const buttonX = msg.x + (msg.width || 200) - CanvasPopupReporter.BUTTON_SIZE - CanvasPopupReporter.BUTTON_PADDING;
532
+ const buttonY = msg.y + CanvasPopupReporter.BUTTON_PADDING;
533
+ if (x >= buttonX && x <= buttonX + CanvasPopupReporter.BUTTON_SIZE && y >= buttonY && y <= buttonY + CanvasPopupReporter.BUTTON_SIZE) {
534
+ const index = this.messages.findIndex((m) => m.id === msg.id);
535
+ if (index !== -1) this.messages.splice(index, 1);
536
+ return;
537
+ }
538
+ }
539
+ }
540
+ resizeCanvas() {
541
+ if (!this.canvas) return;
542
+ this.canvas.width = window.innerWidth;
543
+ this.canvas.height = window.innerHeight;
544
+ this.ctx = this.canvas.getContext("2d");
545
+ }
546
+ getColorForType(type) {
547
+ const colors = {
548
+ trace: "#9E9E9E",
549
+ debug: "#607D8B",
550
+ info: "#2196F3",
551
+ log: "#2196F3",
552
+ warn: "#FFC107",
553
+ error: "#D32F2F",
554
+ fatal: "#7B1FA2",
555
+ success: "#4CAF50",
556
+ start: "#00BCD4",
557
+ box: "#9E9E9E",
558
+ ready: "#4CAF50",
559
+ fail: "#D32F2F"
560
+ };
561
+ return colors[type] || colors.log || "#616161";
562
+ }
563
+ getTypeLabel(type) {
564
+ return type || "log";
565
+ }
566
+ getFileInfo() {
567
+ try {
568
+ const stack = (/* @__PURE__ */ new Error("Stack trace")).stack;
569
+ if (!stack) return null;
570
+ const skipPatterns = [
571
+ "CanvasPopupReporter",
572
+ "browser.js",
573
+ "consola",
574
+ "createLogger"
575
+ ];
576
+ const lines = stack.split("\n").slice(4, 15);
577
+ for (const line of lines) {
578
+ const trimmed = line.trim();
579
+ if (!skipPatterns.some((pattern) => trimmed.includes(pattern))) {
580
+ const match = trimmed.match(/\(?([^()]+):(\d+):(\d+)\)?/) || trimmed.match(/at\s+[^(]*\(?([^:]+):(\d+):(\d+)\)?/);
581
+ if (match) {
582
+ let file = match[1].trim();
583
+ const lineNum = match[2];
584
+ if (file.includes("://")) file = file.slice(file.indexOf("://") + 3).slice(file.indexOf("/"));
585
+ if (file.includes("/src/")) file = file.slice(file.indexOf("/src/") + 5);
586
+ const queryIndex = file.indexOf("?");
587
+ if (queryIndex > 0) file = file.slice(0, queryIndex);
588
+ const hashIndex = file.indexOf("#");
589
+ if (hashIndex > 0) file = file.slice(0, hashIndex);
590
+ if (file.startsWith("/")) file = file.slice(1);
591
+ if (file && lineNum) return {
592
+ file,
593
+ line: parseInt(lineNum, 10),
594
+ column: parseInt(match[3], 10),
595
+ fullPath: match[0]
596
+ };
597
+ }
598
+ }
599
+ }
600
+ } catch {}
601
+ return null;
602
+ }
603
+ formatMessage(logObj) {
604
+ const args = logObj.args || [];
605
+ if (args.length === 0) return "";
606
+ const message = args.map((arg) => {
607
+ if (arg instanceof Error) {
608
+ let msg = `${arg.name || "Error"}: ${arg.message || ""}`;
609
+ if (arg.stack && arg.stack !== arg.message) msg += "\n" + arg.stack.split("\n").slice(0, 3).join("\n");
610
+ return msg;
611
+ }
612
+ if (arg === null) return "null";
613
+ if (arg === void 0) return "undefined";
614
+ if (typeof arg === "object") try {
615
+ const str = arg.toString?.();
616
+ if (str && str !== "[object Object]") return str;
617
+ return JSON.stringify(arg, null, 2);
618
+ } catch {
619
+ return String(arg);
620
+ }
621
+ return String(arg);
622
+ }).join(" ");
623
+ return message.length > CanvasPopupReporter.MAX_MESSAGE_LENGTH ? message.slice(0, CanvasPopupReporter.MAX_MESSAGE_LENGTH) + "..." : message;
624
+ }
625
+ calculateMessageHeight(message, maxWidth) {
626
+ if (!this.ctx) return CanvasPopupReporter.MIN_HEIGHT;
627
+ this.ctx.save();
628
+ this.ctx.font = CanvasPopupReporter.FONT_TEXT;
629
+ const words = message.split(" ");
630
+ const lines = [];
631
+ let currentLine = "";
632
+ for (const word of words) {
633
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
634
+ if (this.ctx.measureText(testLine).width > maxWidth && currentLine) {
635
+ lines.push(currentLine);
636
+ currentLine = word;
637
+ } else currentLine = testLine;
638
+ }
639
+ if (currentLine) lines.push(currentLine);
640
+ this.ctx.restore();
641
+ return Math.max(CanvasPopupReporter.MIN_HEIGHT, lines.length * CanvasPopupReporter.LINE_HEIGHT + CanvasPopupReporter.PADDING * 2);
642
+ }
643
+ log(logObj) {
644
+ if (!this.canvas || !this.ctx) return;
645
+ const message = this.formatMessage(logObj);
646
+ if (!message) return;
647
+ const type = logObj.type || "log";
648
+ const color = this.getColorForType(type);
649
+ const typeLabel = this.getTypeLabel(type);
650
+ const id = this.messageId++;
651
+ const fileInfo = this.getFileInfo();
652
+ this.ctx.save();
653
+ this.ctx.font = CanvasPopupReporter.FONT_MONO;
654
+ const labelWidth = this.ctx.measureText(typeLabel).width + 16;
655
+ let fileLinkWidth = 0;
656
+ if (fileInfo) {
657
+ const fileLinkText = `${fileInfo.file}:${fileInfo.line}`;
658
+ fileLinkWidth = this.ctx.measureText(fileLinkText).width + 12;
659
+ }
660
+ this.ctx.restore();
661
+ const textMaxWidth = this.canvas.width - 40 - labelWidth - CanvasPopupReporter.PADDING * 2 - CanvasPopupReporter.BUTTON_SIZE - CanvasPopupReporter.BUTTON_PADDING - fileLinkWidth;
662
+ const boxHeight = this.calculateMessageHeight(message, textMaxWidth);
663
+ this.messages.push({
664
+ id,
665
+ text: message,
666
+ color,
667
+ type,
668
+ typeLabel,
669
+ fileInfo,
670
+ timestamp: Date.now(),
671
+ y: this.canvas.height,
672
+ opacity: 1,
673
+ x: 20,
674
+ height: boxHeight,
675
+ width: 0
676
+ });
677
+ }
678
+ animate() {
679
+ if (!this.canvas || !this.ctx) return;
680
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
681
+ let currentY = this.canvas.height - 20;
682
+ for (let i = this.messages.length - 1; i >= 0; i--) {
683
+ const msg = this.messages[i];
684
+ const msgHeight = msg.height || 60;
685
+ const targetY = currentY - msgHeight;
686
+ msg.y += (targetY - msg.y) * .1;
687
+ currentY = targetY - 10;
688
+ this.ctx.save();
689
+ this.ctx.globalAlpha = msg.opacity;
690
+ this.ctx.font = "13px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif";
691
+ this.ctx.textBaseline = "top";
692
+ const padding = CanvasPopupReporter.PADDING;
693
+ const labelPadding = CanvasPopupReporter.LABEL_PADDING;
694
+ const maxWidth = this.canvas.width - 40;
695
+ const lineHeight = CanvasPopupReporter.LINE_HEIGHT;
696
+ this.ctx.font = CanvasPopupReporter.FONT_MONO;
697
+ const labelWidth = this.ctx.measureText(msg.typeLabel || "log").width + labelPadding * 2;
698
+ const buttonSize = CanvasPopupReporter.BUTTON_SIZE;
699
+ const buttonPadding = CanvasPopupReporter.BUTTON_PADDING;
700
+ let fileLinkWidth = 0;
701
+ let fileLinkText = "";
702
+ if (msg.fileInfo) {
703
+ fileLinkText = `${msg.fileInfo.file}:${msg.fileInfo.line}`;
704
+ this.ctx.font = CanvasPopupReporter.FONT_MONO;
705
+ fileLinkWidth = this.ctx.measureText(fileLinkText).width + 12;
706
+ }
707
+ this.ctx.font = CanvasPopupReporter.FONT_TEXT;
708
+ const textMaxWidthForHeight = maxWidth - labelWidth - padding * 2 - fileLinkWidth - buttonSize - buttonPadding;
709
+ const calculatedHeight = this.calculateMessageHeight(msg.text, textMaxWidthForHeight);
710
+ if (msg.height !== calculatedHeight) msg.height = calculatedHeight;
711
+ const words = msg.text.split(" ");
712
+ const lines = [];
713
+ let currentLine = "";
714
+ const textMaxWidth = textMaxWidthForHeight;
715
+ for (const word of words) {
716
+ const testLine = currentLine ? currentLine + " " + word : word;
717
+ if (this.ctx.measureText(testLine).width > textMaxWidth && currentLine) {
718
+ lines.push(currentLine);
719
+ currentLine = word;
720
+ } else currentLine = testLine;
721
+ }
722
+ if (currentLine) lines.push(currentLine);
723
+ const maxTextWidth = lines.length > 0 ? Math.max(...lines.map((line) => this.ctx.measureText(line).width)) : 0;
724
+ const boxWidth = Math.min(labelWidth + padding * 2 + maxTextWidth + fileLinkWidth + buttonSize + buttonPadding, maxWidth);
725
+ const boxHeight = Math.max(msg.height || calculatedHeight, CanvasPopupReporter.MIN_HEIGHT);
726
+ msg.fileLinkX = msg.x + labelWidth + padding * 2 + maxTextWidth;
727
+ msg.fileLinkY = msg.y + padding;
728
+ msg.fileLinkWidth = fileLinkWidth;
729
+ msg.width = boxWidth;
730
+ this.ctx.fillStyle = "#ffffff";
731
+ this.ctx.fillRect(msg.x, msg.y, boxWidth, boxHeight);
732
+ this.ctx.shadowColor = "rgba(0, 0, 0, 0.1)";
733
+ this.ctx.shadowBlur = 8;
734
+ this.ctx.shadowOffsetX = 0;
735
+ this.ctx.shadowOffsetY = 2;
736
+ this.ctx.fillRect(msg.x, msg.y, boxWidth, boxHeight);
737
+ this.ctx.shadowBlur = 0;
738
+ this.ctx.shadowOffsetX = 0;
739
+ this.ctx.shadowOffsetY = 0;
740
+ this.ctx.strokeStyle = "#e0e0e0";
741
+ this.ctx.lineWidth = 1;
742
+ this.ctx.strokeRect(msg.x, msg.y, boxWidth, boxHeight);
743
+ this.ctx.fillStyle = msg.color;
744
+ this.ctx.fillRect(msg.x, msg.y, labelWidth, boxHeight);
745
+ this.ctx.fillStyle = "#ffffff";
746
+ this.ctx.font = CanvasPopupReporter.FONT_MONO;
747
+ this.ctx.fontWeight = "bold";
748
+ const labelTextY = msg.y + boxHeight / 2 - 6;
749
+ this.ctx.textAlign = "center";
750
+ this.ctx.fillText(msg.typeLabel, msg.x + labelWidth / 2, labelTextY);
751
+ this.ctx.textAlign = "left";
752
+ this.ctx.fillStyle = "#212121";
753
+ this.ctx.font = CanvasPopupReporter.FONT_TEXT;
754
+ const textStartX = msg.x + labelWidth + padding;
755
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) this.ctx.fillText(lines[lineIndex], textStartX, msg.y + padding + lineIndex * lineHeight, maxTextWidth);
756
+ if (msg.fileInfo && fileLinkText) {
757
+ this.ctx.fillStyle = "#1976D2";
758
+ this.ctx.font = CanvasPopupReporter.FONT_MONO;
759
+ this.ctx.textAlign = "right";
760
+ const fileLinkX = msg.x + boxWidth - buttonSize - buttonPadding - 6;
761
+ const fileLinkY = msg.y + padding + 2;
762
+ this.ctx.fillText(fileLinkText, fileLinkX, fileLinkY);
763
+ this.ctx.textAlign = "left";
764
+ this.ctx.strokeStyle = "#1976D2";
765
+ this.ctx.lineWidth = 1;
766
+ const textMetrics = this.ctx.measureText(fileLinkText);
767
+ this.ctx.beginPath();
768
+ this.ctx.moveTo(fileLinkX - textMetrics.width, fileLinkY + 11);
769
+ this.ctx.lineTo(fileLinkX, fileLinkY + 11);
770
+ this.ctx.stroke();
771
+ }
772
+ const buttonX = msg.x + boxWidth - buttonSize - buttonPadding;
773
+ const buttonY = msg.y + buttonPadding;
774
+ this.ctx.strokeStyle = "#999999";
775
+ this.ctx.lineWidth = 1.5;
776
+ this.ctx.lineCap = "round";
777
+ const crossPadding = 4;
778
+ this.ctx.beginPath();
779
+ this.ctx.moveTo(buttonX + crossPadding, buttonY + crossPadding);
780
+ this.ctx.lineTo(buttonX + buttonSize - crossPadding, buttonY + buttonSize - crossPadding);
781
+ this.ctx.moveTo(buttonX + buttonSize - crossPadding, buttonY + crossPadding);
782
+ this.ctx.lineTo(buttonX + crossPadding, buttonY + buttonSize - crossPadding);
783
+ this.ctx.stroke();
784
+ this.ctx.restore();
785
+ }
786
+ requestAnimationFrame(() => this.animate());
787
+ }
788
+ };
789
+ isPopupDebugEnabled = false;
790
+ if (typeof document !== "undefined") try {
791
+ const envValue = import.meta.env?.VITE_CONSOLA_POPUP_DEBUG;
792
+ if (envValue !== void 0 && envValue !== null && envValue !== "") isPopupDebugEnabled = String(envValue).toLowerCase() === "true" || envValue === true;
793
+ } catch {}
794
+ if (!isPopupDebugEnabled && typeof process !== "undefined" && process.env) {
795
+ const processValue = process.env.VITE_CONSOLA_POPUP_DEBUG;
796
+ if (processValue) isPopupDebugEnabled = String(processValue).toLowerCase() === "true";
797
+ }
798
+ canvasPopupReporter = null;
799
+ if (isPopupDebugEnabled && typeof document !== "undefined") canvasPopupReporter = new CanvasPopupReporter();
800
+ defaultConsola = consola.create(options);
801
+ if (isPopupDebugEnabled && typeof document !== "undefined" && canvasPopupReporter) defaultConsola.addReporter(canvasPopupReporter);
802
+ }));
803
+
804
+ //#endregion
805
+ //#region src/web.js
806
+ var web_exports = /* @__PURE__ */ __exportAll({ ZebraWeb: () => ZebraWeb });
807
+ var ZebraWeb;
808
+ var init_web = __esmMin((() => {
809
+ init_browser();
810
+ ZebraWeb = class extends WebPlugin {
811
+ async print(zpl) {
812
+ try {
813
+ defaultConsola.debug("Запит на підключення до принтера...", "info");
814
+ const port = await navigator.serial.requestPort();
815
+ defaultConsola.debug("Порт вибрано, відкриваємо з'єднання...", "info");
816
+ await port.open({
817
+ baudRate: 9600,
818
+ dataBits: 8,
819
+ parity: "none",
820
+ stopBits: 1,
821
+ flowControl: "none"
822
+ });
823
+ defaultConsola.debug("З'єднання відкрито!", "success");
824
+ port.writable.getWriter();
825
+ isConnected.value = true;
826
+ defaultConsola.debug("Принтер готовий до друку", "success");
827
+ } catch (error) {
828
+ defaultConsola.debug(`Помилка підключення: ${error.message}`, "error");
829
+ isConnected.value = false;
830
+ }
831
+ console.consola.debug("print from web capacitor plugin", zpl);
832
+ return true;
833
+ }
834
+ };
835
+ }));
836
+
837
+ //#endregion
838
+ //#region src/index.js
839
+ const Zebra = registerPlugin("Zebra", { web: () => Promise.resolve().then(() => (init_web(), web_exports)).then((m) => new m.ZebraWeb()) });
840
+
841
+ //#endregion
842
+ export { Zebra };
843
+ //# sourceMappingURL=plugin.js.map