@ruhiverse/thermal-printer-plugin 1.0.7 → 1.0.9

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.
@@ -4,6 +4,8 @@ import android.Manifest;
4
4
  import android.app.Activity;
5
5
  import android.app.AlertDialog;
6
6
  import android.app.PendingIntent;
7
+ import android.bluetooth.BluetoothAdapter;
8
+ import android.bluetooth.BluetoothDevice;
7
9
  import android.content.BroadcastReceiver;
8
10
  import android.content.Context;
9
11
  import android.content.Intent;
@@ -11,6 +13,8 @@ import android.content.IntentFilter;
11
13
  import android.content.pm.PackageManager;
12
14
  import android.hardware.usb.UsbDevice;
13
15
  import android.hardware.usb.UsbManager;
16
+ import android.os.Handler;
17
+ import android.os.Looper;
14
18
  import android.util.Log;
15
19
  import androidx.core.app.ActivityCompat;
16
20
  import androidx.core.content.ContextCompat;
@@ -35,6 +39,8 @@ import com.ruhiverse.thermalprinter.AsyncBluetoothEscPosPrint;
35
39
  import com.ruhiverse.thermalprinter.AsyncEscPosPrint;
36
40
  import com.ruhiverse.thermalprinter.AsyncEscPosPrinter;
37
41
  import com.ruhiverse.thermalprinter.AsyncUsbEscPosPrint;
42
+ import java.util.HashSet;
43
+ import java.util.Set;
38
44
 
39
45
  @CapacitorPlugin(
40
46
  name = "ThermalPrinter",
@@ -60,6 +66,12 @@ public class ThermalPrinterPlugin extends Plugin {
60
66
  public static final int PERMISSION_BLUETOOTH_CONNECT = 3;
61
67
  public static final int PERMISSION_BLUETOOTH_SCAN = 4;
62
68
  private static final String ACTION_USB_PERMISSION = "com.ruhiverse.thermalprinter.USB_PERMISSION";
69
+
70
+ // Bluetooth discovery
71
+ private Set<BluetoothDevice> discoveredDevices = new HashSet<>();
72
+ private PluginCall discoveryCall;
73
+ private BroadcastReceiver discoveryReceiver;
74
+ private Handler discoveryHandler = new Handler(Looper.getMainLooper());
63
75
 
64
76
  private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
65
77
  public void onReceive(Context context, Intent intent) {
@@ -263,43 +275,203 @@ public class ThermalPrinterPlugin extends Plugin {
263
275
  this.checkBluetoothPermissions(
264
276
  () -> {
265
277
  try {
266
- BluetoothConnection[] bluetoothPrinters = (new BluetoothPrintersConnections()).getList();
267
278
  JSArray printers = new JSArray();
268
- if (bluetoothPrinters != null) {
279
+ discoveredDevices.clear();
280
+ discoveryCall = call;
281
+
282
+ // First, get paired printers
283
+ BluetoothConnection[] bluetoothPrinters = (new BluetoothPrintersConnections()).getList();
284
+ Set<String> pairedAddresses = new HashSet<>();
285
+
286
+ if (bluetoothPrinters != null && bluetoothPrinters.length > 0) {
269
287
  for (BluetoothConnection printer : bluetoothPrinters) {
270
- JSObject printerObj = new JSObject();
271
- printerObj.put("name", printer.getDevice().getName());
272
- printerObj.put("address", printer.getDevice().getAddress());
273
- printers.put(printerObj);
288
+ try {
289
+ JSObject printerObj = new JSObject();
290
+ String name = printer.getDevice().getName();
291
+ String address = printer.getDevice().getAddress();
292
+
293
+ // Handle null/empty names
294
+ if (name == null || name.isEmpty()) {
295
+ name = "Unknown Device";
296
+ }
297
+
298
+ printerObj.put("name", name);
299
+ printerObj.put("address", address != null ? address : "");
300
+ printers.put(printerObj);
301
+ pairedAddresses.add(address);
302
+
303
+ Log.d(TAG, "Found paired Bluetooth printer: " + name + " (" + address + ")");
304
+ } catch (Exception e) {
305
+ Log.e(TAG, "Error processing printer: " + e.getMessage());
306
+ }
274
307
  }
308
+ } else {
309
+ Log.d(TAG, "No paired Bluetooth printers found. Starting discovery...");
275
310
  }
276
- JSObject ret = new JSObject();
277
- ret.put("printers", printers);
278
- call.resolve(ret);
311
+
312
+ // Start Bluetooth discovery for unpaired devices
313
+ startBluetoothDiscovery(printers, pairedAddresses);
279
314
  } catch (Exception e) {
315
+ Log.e(TAG, "Error listing Bluetooth printers: " + e.getMessage(), e);
280
316
  call.reject("Error listing Bluetooth printers: " + e.getMessage());
281
317
  }
282
318
  }
283
319
  );
284
320
  }
321
+
322
+ private void startBluetoothDiscovery(JSArray printers, java.util.Set<String> pairedAddresses) {
323
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
324
+
325
+ if (bluetoothAdapter == null) {
326
+ Log.d(TAG, "Bluetooth not supported on this device");
327
+ JSObject ret = new JSObject();
328
+ ret.put("printers", printers);
329
+ if (discoveryCall != null) {
330
+ discoveryCall.resolve(ret);
331
+ discoveryCall = null;
332
+ }
333
+ return;
334
+ }
335
+
336
+ if (!bluetoothAdapter.isEnabled()) {
337
+ Log.d(TAG, "Bluetooth is not enabled");
338
+ JSObject ret = new JSObject();
339
+ ret.put("printers", printers);
340
+ if (discoveryCall != null) {
341
+ discoveryCall.resolve(ret);
342
+ discoveryCall = null;
343
+ }
344
+ return;
345
+ }
346
+
347
+ // Register receiver for discovered devices
348
+ discoveryReceiver = new BroadcastReceiver() {
349
+ @Override
350
+ public void onReceive(Context context, Intent intent) {
351
+ String action = intent.getAction();
352
+ if (BluetoothDevice.ACTION_FOUND.equals(action)) {
353
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
354
+ if (device != null && !pairedAddresses.contains(device.getAddress())) {
355
+ discoveredDevices.add(device);
356
+ String name = device.getName();
357
+ if (name == null || name.isEmpty()) {
358
+ name = "Unknown Device";
359
+ }
360
+ Log.d(TAG, "Discovered Bluetooth device: " + name + " (" + device.getAddress() + ")");
361
+ }
362
+ } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
363
+ // Discovery finished, add discovered devices to results
364
+ JSArray finalPrinters = printers;
365
+ for (BluetoothDevice device : discoveredDevices) {
366
+ try {
367
+ JSObject printerObj = new JSObject();
368
+ String name = device.getName();
369
+ if (name == null || name.isEmpty()) {
370
+ name = "Unknown Device";
371
+ }
372
+ printerObj.put("name", name);
373
+ printerObj.put("address", device.getAddress());
374
+ finalPrinters.put(printerObj);
375
+ Log.d(TAG, "Added discovered device: " + name + " (" + device.getAddress() + ")");
376
+ } catch (Exception e) {
377
+ Log.e(TAG, "Error adding discovered device: " + e.getMessage());
378
+ }
379
+ }
380
+
381
+ // Unregister receiver
382
+ try {
383
+ getContext().unregisterReceiver(discoveryReceiver);
384
+ } catch (Exception e) {
385
+ Log.e(TAG, "Error unregistering receiver: " + e.getMessage());
386
+ }
387
+
388
+ // Return results
389
+ JSObject ret = new JSObject();
390
+ ret.put("printers", finalPrinters);
391
+ if (discoveryCall != null) {
392
+ discoveryCall.resolve(ret);
393
+ discoveryCall = null;
394
+ }
395
+ discoveryReceiver = null;
396
+ }
397
+ }
398
+ };
399
+
400
+ // Register receiver
401
+ IntentFilter filter = new IntentFilter();
402
+ filter.addAction(BluetoothDevice.ACTION_FOUND);
403
+ filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
404
+ getContext().registerReceiver(discoveryReceiver, filter);
405
+
406
+ // Start discovery
407
+ if (bluetoothAdapter.isDiscovering()) {
408
+ bluetoothAdapter.cancelDiscovery();
409
+ }
410
+
411
+ Log.d(TAG, "Starting Bluetooth discovery...");
412
+ boolean started = bluetoothAdapter.startDiscovery();
413
+
414
+ if (!started) {
415
+ Log.d(TAG, "Failed to start Bluetooth discovery");
416
+ try {
417
+ getContext().unregisterReceiver(discoveryReceiver);
418
+ } catch (Exception e) {
419
+ Log.e(TAG, "Error unregistering receiver: " + e.getMessage());
420
+ }
421
+ JSObject ret = new JSObject();
422
+ ret.put("printers", printers);
423
+ if (discoveryCall != null) {
424
+ discoveryCall.resolve(ret);
425
+ discoveryCall = null;
426
+ }
427
+ discoveryReceiver = null;
428
+ } else {
429
+ // Stop discovery after 8 seconds
430
+ discoveryHandler.postDelayed(() -> {
431
+ if (bluetoothAdapter.isDiscovering()) {
432
+ bluetoothAdapter.cancelDiscovery();
433
+ Log.d(TAG, "Discovery timeout, stopping scan");
434
+ }
435
+ }, 8000);
436
+ }
437
+ }
285
438
 
286
439
  @PluginMethod
287
440
  public void listUsbPrinters(PluginCall call) {
288
441
  try {
289
442
  UsbConnection[] usbPrinters = (new UsbPrintersConnections(getContext())).getList();
290
443
  JSArray printers = new JSArray();
291
- if (usbPrinters != null) {
444
+
445
+ if (usbPrinters != null && usbPrinters.length > 0) {
292
446
  for (UsbConnection printer : usbPrinters) {
293
- JSObject printerObj = new JSObject();
294
- printerObj.put("name", printer.getDevice().getDeviceName());
295
- printerObj.put("address", printer.getDevice().getSerialNumber()); // Using serial number as address for USB
296
- printers.put(printerObj);
447
+ try {
448
+ JSObject printerObj = new JSObject();
449
+ String name = printer.getDevice().getDeviceName();
450
+ String serial = printer.getDevice().getSerialNumber();
451
+
452
+ // Handle null/empty names
453
+ if (name == null || name.isEmpty()) {
454
+ name = "USB Printer";
455
+ }
456
+
457
+ printerObj.put("name", name);
458
+ printerObj.put("address", serial != null ? serial : printer.getDevice().getDeviceId() + "");
459
+ printers.put(printerObj);
460
+
461
+ Log.d(TAG, "Found USB printer: " + name + " (ID: " + printer.getDevice().getDeviceId() + ")");
462
+ } catch (Exception e) {
463
+ Log.e(TAG, "Error processing USB printer: " + e.getMessage());
464
+ }
297
465
  }
466
+ } else {
467
+ Log.d(TAG, "No USB printers found. Make sure printer is connected via USB.");
298
468
  }
469
+
299
470
  JSObject ret = new JSObject();
300
471
  ret.put("printers", printers);
301
472
  call.resolve(ret);
302
473
  } catch (Exception e) {
474
+ Log.e(TAG, "Error listing USB printers: " + e.getMessage(), e);
303
475
  call.reject("Error listing USB printers: " + e.getMessage());
304
476
  }
305
477
  }
@@ -283,22 +283,27 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
283
283
  }
284
284
 
285
285
  @objc func listBluetoothPrinters(_ call: CAPPluginCall) {
286
+ print("ThermalPrinter: listBluetoothPrinters called")
286
287
  var printers: [[String: String]] = []
287
288
 
288
289
  // First, get MFi/ExternalAccessory printers
289
290
  let manager = EAAccessoryManager.shared()
290
291
  let accessories = manager.connectedAccessories
291
292
 
293
+ print("ThermalPrinter: Found \(accessories.count) ExternalAccessory devices")
294
+
292
295
  let mfiPrinters: [[String: String]] = accessories.compactMap { accessory in
293
296
  guard accessory.protocolStrings.contains(where: { supportedPrinterProtocols.contains($0) }) else {
294
297
  return nil
295
298
  }
299
+ print("ThermalPrinter: Found MFi printer: \(accessory.name)")
296
300
  return [
297
301
  "name": accessory.name,
298
302
  "address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
299
303
  ]
300
304
  }
301
305
  printers.append(contentsOf: mfiPrinters)
306
+ print("ThermalPrinter: Found \(mfiPrinters.count) MFi printers")
302
307
 
303
308
  // Then scan for BLE printers
304
309
  discoveredPeripherals.removeAll()
@@ -306,30 +311,40 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
306
311
 
307
312
  // Initialize central manager if needed
308
313
  if centralManager == nil {
314
+ print("ThermalPrinter: Initializing CBCentralManager")
309
315
  centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
310
316
  }
311
317
 
312
318
  guard let centralManager = centralManager else {
319
+ print("ThermalPrinter: Central manager is nil, returning \(printers.count) printers")
313
320
  call.resolve(["printers": printers])
314
321
  return
315
322
  }
316
323
 
324
+ print("ThermalPrinter: Bluetooth state: \(centralManager.state.rawValue)")
325
+
317
326
  // Check Bluetooth state
318
327
  switch centralManager.state {
319
328
  case .poweredOn:
320
329
  // Start scanning immediately
330
+ print("ThermalPrinter: Bluetooth powered on, starting scan")
321
331
  startBLEScan(for: call, existingPrinters: printers)
322
332
  case .poweredOff:
333
+ print("ThermalPrinter: Bluetooth powered off, returning \(printers.count) printers")
323
334
  call.resolve(["printers": printers])
324
335
  case .unauthorized:
336
+ print("ThermalPrinter: Bluetooth unauthorized")
325
337
  call.reject("Bluetooth access denied. Please enable Bluetooth permissions in Settings.")
326
338
  case .unsupported:
339
+ print("ThermalPrinter: Bluetooth unsupported")
327
340
  call.reject("Bluetooth is not supported on this device.")
328
341
  case .resetting, .unknown:
342
+ print("ThermalPrinter: Bluetooth state unknown/resetting, waiting for update")
329
343
  // Wait for state update
330
344
  // The state will be updated via centralManagerDidUpdateState
331
345
  break
332
346
  @unknown default:
347
+ print("ThermalPrinter: Unknown Bluetooth state, returning \(printers.count) printers")
333
348
  call.resolve(["printers": printers])
334
349
  }
335
350
  }
@@ -345,28 +360,32 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
345
360
  return
346
361
  }
347
362
 
363
+ // Clear previous discoveries
364
+ discoveredPeripherals.removeAll()
365
+
348
366
  // Scan for all peripherals
367
+ print("ThermalPrinter: Starting BLE scan...")
349
368
  centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
350
369
 
351
- // Stop scanning after 5 seconds
352
- DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
370
+ // Stop scanning after 8 seconds (increased from 5 to give more time)
371
+ DispatchQueue.main.asyncAfter(deadline: .now() + 8.0) {
353
372
  centralManager.stopScan()
373
+ print("ThermalPrinter: Stopped BLE scan. Found \(self.discoveredPeripherals.count) peripherals")
354
374
 
355
375
  var printers = existingPrinters
356
376
 
357
- // Add discovered BLE printers - return all devices with names
377
+ // Add discovered BLE printers - return all devices, even without names
358
378
  let blePrinters: [[String: String]] = self.discoveredPeripherals.compactMap { peripheral in
359
- // Return all peripherals with names, not just those matching keywords
360
- // This allows users to see all Bluetooth devices and select their printer
361
- guard let name = peripheral.name, !name.isEmpty else {
362
- return nil
363
- }
379
+ // Include all peripherals, even if they don't have names
380
+ // Some printers don't advertise names until connected
381
+ let name = peripheral.name ?? "Unknown Device"
364
382
  return [
365
383
  "name": name,
366
384
  "address": peripheral.identifier.uuidString,
367
385
  ]
368
386
  }
369
387
 
388
+ print("ThermalPrinter: Returning \(blePrinters.count) BLE printers")
370
389
  printers.append(contentsOf: blePrinters)
371
390
  call.resolve(["printers": printers])
372
391
  self.listPrintersCall = nil
@@ -376,10 +395,13 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
376
395
  // MARK: - CBCentralManagerDelegate
377
396
 
378
397
  public func centralManagerDidUpdateState(_ central: CBCentralManager) {
398
+ print("ThermalPrinter: Central manager state updated: \(central.state.rawValue)")
399
+
379
400
  // If we're waiting to list printers, start scanning now
380
401
  if let call = listPrintersCall {
381
402
  switch central.state {
382
403
  case .poweredOn:
404
+ print("ThermalPrinter: Bluetooth now powered on, starting scan")
383
405
  // Get existing MFi printers first
384
406
  let manager = EAAccessoryManager.shared()
385
407
  let accessories = manager.connectedAccessories
@@ -394,15 +416,19 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
394
416
  }
395
417
  startBLEScan(for: call, existingPrinters: mfiPrinters)
396
418
  case .poweredOff:
419
+ print("ThermalPrinter: Bluetooth powered off")
397
420
  call.resolve(["printers": []])
398
421
  listPrintersCall = nil
399
422
  case .unauthorized:
423
+ print("ThermalPrinter: Bluetooth unauthorized")
400
424
  call.reject("Bluetooth access denied. Please enable Bluetooth permissions in Settings.")
401
425
  listPrintersCall = nil
402
426
  case .unsupported:
427
+ print("ThermalPrinter: Bluetooth unsupported")
403
428
  call.reject("Bluetooth is not supported on this device.")
404
429
  listPrintersCall = nil
405
430
  default:
431
+ print("ThermalPrinter: Bluetooth state: \(central.state.rawValue)")
406
432
  break
407
433
  }
408
434
  }
@@ -412,6 +438,8 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
412
438
  // Avoid duplicates
413
439
  if !discoveredPeripherals.contains(where: { $0.identifier == peripheral.identifier }) {
414
440
  discoveredPeripherals.append(peripheral)
441
+ let name = peripheral.name ?? "Unknown"
442
+ print("ThermalPrinter: Discovered peripheral: \(name) (\(peripheral.identifier.uuidString))")
415
443
  }
416
444
 
417
445
  // If we're looking for a specific printer to print to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruhiverse/thermal-printer-plugin",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Capacitor plugin for thermal printing via USB and Bluetooth",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",