@ruhiverse/thermal-printer-plugin 1.0.9 → 1.0.13
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/README.md +56 -51
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/ruhiverse/thermalprinter/ThermalPrinterPlugin.java +63 -141
- package/dist/docs.json +4 -4
- package/dist/esm/definitions.d.ts +10 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/ThermalPrinterPlugin.swift +52 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,22 +136,28 @@ Returns a list of connected USB printers.
|
|
|
136
136
|
|
|
137
137
|
## Platform Support
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
| Platform | Bluetooth | USB | Printer Discovery |
|
|
140
|
+
| -------- | --------- | --- | ----------------- |
|
|
141
|
+
| Android | ✅ | ✅ | ✅ (paired + scan) |
|
|
142
|
+
| iOS | ✅ (MFi & BLE) | ✅ (MFi only) | ✅ (MFi + BLE scan) |
|
|
143
|
+
| Web | ❌ | ❌ | ❌ |
|
|
142
144
|
|
|
143
145
|
## Android Setup
|
|
144
146
|
|
|
145
147
|
### Permissions
|
|
146
148
|
|
|
147
|
-
The plugin
|
|
149
|
+
The plugin declares the following permissions in its `AndroidManifest.xml`:
|
|
148
150
|
|
|
149
151
|
- `BLUETOOTH`
|
|
150
152
|
- `BLUETOOTH_ADMIN`
|
|
151
153
|
- `BLUETOOTH_CONNECT` (Android 12+)
|
|
152
154
|
- `BLUETOOTH_SCAN` (Android 12+)
|
|
155
|
+
- `ACCESS_FINE_LOCATION` (required for Bluetooth scanning on Android 6-11)
|
|
156
|
+
- `ACCESS_COARSE_LOCATION`
|
|
153
157
|
- `USB_PERMISSION`
|
|
154
158
|
|
|
159
|
+
The plugin automatically requests Bluetooth permissions at runtime when needed.
|
|
160
|
+
|
|
155
161
|
### USB Printing
|
|
156
162
|
|
|
157
163
|
1. Connect your thermal printer via USB
|
|
@@ -160,20 +166,21 @@ The plugin automatically requests the following permissions:
|
|
|
160
166
|
|
|
161
167
|
### Bluetooth Printing
|
|
162
168
|
|
|
163
|
-
1. Pair your thermal printer with the device via Bluetooth
|
|
164
|
-
2.
|
|
169
|
+
1. Pair your thermal printer with the device via **Android Settings > Bluetooth**
|
|
170
|
+
2. Use `listBluetoothPrinters()` to get the list of paired devices
|
|
171
|
+
3. Use `printByBluetooth()` with the printer's `address` to print, or omit `address` to use the first paired printer
|
|
165
172
|
|
|
166
173
|
## iOS Setup
|
|
167
174
|
|
|
168
|
-
### Info.plist Configuration
|
|
175
|
+
### Required Info.plist Configuration
|
|
169
176
|
|
|
170
|
-
Add the following to your app's `Info.plist
|
|
177
|
+
Add the following keys to your app's `Info.plist`. **Without these, Bluetooth will not work on iOS.**
|
|
171
178
|
|
|
172
179
|
```xml
|
|
173
180
|
<key>NSBluetoothAlwaysUsageDescription</key>
|
|
174
|
-
<string>This app needs Bluetooth access to connect to thermal printers
|
|
181
|
+
<string>This app needs Bluetooth access to connect to thermal printers.</string>
|
|
175
182
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
|
176
|
-
<string>This app needs Bluetooth access to connect to thermal printers
|
|
183
|
+
<string>This app needs Bluetooth access to connect to thermal printers.</string>
|
|
177
184
|
<key>UISupportedExternalAccessoryProtocols</key>
|
|
178
185
|
<array>
|
|
179
186
|
<string>com.epson.escpos</string>
|
|
@@ -181,72 +188,70 @@ Add the following to your app's `Info.plist`:
|
|
|
181
188
|
</array>
|
|
182
189
|
```
|
|
183
190
|
|
|
184
|
-
|
|
191
|
+
| Key | Required | Description |
|
|
192
|
+
| --- | -------- | ----------- |
|
|
193
|
+
| `NSBluetoothAlwaysUsageDescription` | **Yes** | iOS will silently block Bluetooth access without this. Triggers the permission dialog. |
|
|
194
|
+
| `NSBluetoothPeripheralUsageDescription` | **Yes** | Required for connecting to Bluetooth peripherals (iOS 12 and earlier). |
|
|
195
|
+
| `UISupportedExternalAccessoryProtocols` | **Yes** (for MFi printers) | Lists the protocol strings your printer supports. Update with your printer's protocols. |
|
|
185
196
|
|
|
186
|
-
|
|
197
|
+
### iOS Bluetooth Printing
|
|
187
198
|
|
|
188
|
-
|
|
199
|
+
The plugin supports two types of Bluetooth connections on iOS:
|
|
189
200
|
|
|
190
|
-
|
|
201
|
+
**MFi / ExternalAccessory printers (classic Bluetooth):**
|
|
202
|
+
- Requires the printer to be MFi-certified (Made for iPhone)
|
|
203
|
+
- Uses the ExternalAccessory framework
|
|
204
|
+
- Printer must be paired in iOS Settings > Bluetooth
|
|
205
|
+
- Protocol strings must be listed in `UISupportedExternalAccessoryProtocols`
|
|
191
206
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
207
|
+
**BLE printers (Bluetooth Low Energy):**
|
|
208
|
+
- Uses CoreBluetooth framework
|
|
209
|
+
- Discovered via BLE scanning (4-second scan)
|
|
210
|
+
- Use the `address` from `listBluetoothPrinters()` when calling `printByBluetooth()`
|
|
196
211
|
|
|
197
|
-
|
|
212
|
+
### iOS USB Printing
|
|
198
213
|
|
|
199
|
-
|
|
200
|
-
cd /path/to/milkwala/project
|
|
201
|
-
npm link @ruhiverse/thermal-printer-plugin
|
|
202
|
-
npx cap sync
|
|
203
|
-
```
|
|
214
|
+
USB printing on iOS requires MFi (Made for iPhone) certification for the printer. The printer is accessed through the ExternalAccessory framework.
|
|
204
215
|
|
|
205
|
-
|
|
216
|
+
### iOS Troubleshooting
|
|
206
217
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
```
|
|
218
|
+
| Issue | Solution |
|
|
219
|
+
| ----- | -------- |
|
|
220
|
+
| `listBluetoothPrinters()` returns empty | Make sure `NSBluetoothAlwaysUsageDescription` is in your Info.plist and Bluetooth permission is granted |
|
|
221
|
+
| Printer not found (MFi) | Verify the printer's protocol string is listed in `UISupportedExternalAccessoryProtocols` |
|
|
222
|
+
| Printer not found (BLE) | Ensure the printer is powered on and in range. BLE scan runs for 4 seconds |
|
|
223
|
+
| Permission dialog not showing | Delete and reinstall the app to reset permissions |
|
|
214
224
|
|
|
215
|
-
|
|
225
|
+
## Local Development
|
|
216
226
|
|
|
217
|
-
|
|
227
|
+
### Testing Locally Before Publishing
|
|
218
228
|
|
|
219
|
-
|
|
220
|
-
cd /path/to/milkwala/project
|
|
221
|
-
npm install /Users/apple/Desktop/ionic/workspace/plugin/thermal-printer-plugin
|
|
222
|
-
npx cap sync
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## Publishing to npm
|
|
226
|
-
|
|
227
|
-
1. **Login to npm (if not already logged in):**
|
|
229
|
+
1. **Link the plugin locally:**
|
|
228
230
|
|
|
229
231
|
```bash
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
cd path/to/thermal-printer-plugin
|
|
233
|
+
npm link
|
|
232
234
|
```
|
|
233
235
|
|
|
234
|
-
2. **
|
|
236
|
+
2. **In your Ionic/Capacitor project:**
|
|
235
237
|
|
|
236
238
|
```bash
|
|
237
|
-
|
|
239
|
+
cd path/to/your-project
|
|
240
|
+
npm link @ruhiverse/thermal-printer-plugin
|
|
241
|
+
npx cap sync
|
|
238
242
|
```
|
|
239
243
|
|
|
240
|
-
3. **
|
|
244
|
+
3. **Build and test:**
|
|
241
245
|
|
|
242
246
|
```bash
|
|
243
|
-
|
|
247
|
+
npx cap run android
|
|
248
|
+
npx cap run ios
|
|
244
249
|
```
|
|
245
250
|
|
|
246
|
-
|
|
251
|
+
### Alternative: Install from local path
|
|
247
252
|
|
|
248
253
|
```bash
|
|
249
|
-
npm install
|
|
254
|
+
npm install path/to/thermal-printer-plugin
|
|
250
255
|
npx cap sync
|
|
251
256
|
```
|
|
252
257
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
|
3
3
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
|
4
4
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
5
|
-
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
|
5
|
+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
|
|
6
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
7
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
6
8
|
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
|
7
9
|
</manifest>
|
|
@@ -13,8 +13,6 @@ import android.content.IntentFilter;
|
|
|
13
13
|
import android.content.pm.PackageManager;
|
|
14
14
|
import android.hardware.usb.UsbDevice;
|
|
15
15
|
import android.hardware.usb.UsbManager;
|
|
16
|
-
import android.os.Handler;
|
|
17
|
-
import android.os.Looper;
|
|
18
16
|
import android.util.Log;
|
|
19
17
|
import androidx.core.app.ActivityCompat;
|
|
20
18
|
import androidx.core.content.ContextCompat;
|
|
@@ -67,11 +65,7 @@ public class ThermalPrinterPlugin extends Plugin {
|
|
|
67
65
|
public static final int PERMISSION_BLUETOOTH_SCAN = 4;
|
|
68
66
|
private static final String ACTION_USB_PERMISSION = "com.ruhiverse.thermalprinter.USB_PERMISSION";
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
private Set<BluetoothDevice> discoveredDevices = new HashSet<>();
|
|
72
|
-
private PluginCall discoveryCall;
|
|
73
|
-
private BroadcastReceiver discoveryReceiver;
|
|
74
|
-
private Handler discoveryHandler = new Handler(Looper.getMainLooper());
|
|
68
|
+
|
|
75
69
|
|
|
76
70
|
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
|
|
77
71
|
public void onReceive(Context context, Intent intent) {
|
|
@@ -276,164 +270,92 @@ public class ThermalPrinterPlugin extends Plugin {
|
|
|
276
270
|
() -> {
|
|
277
271
|
try {
|
|
278
272
|
JSArray printers = new JSArray();
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
Set<String> addedAddresses = new HashSet<>();
|
|
274
|
+
|
|
275
|
+
// Method 1: Get ALL paired Bluetooth devices directly from BluetoothAdapter
|
|
276
|
+
// This is more reliable than BluetoothPrintersConnections which filters by device class
|
|
277
|
+
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
281
278
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
279
|
+
if (bluetoothAdapter == null) {
|
|
280
|
+
Log.e(TAG, "Bluetooth not supported on this device");
|
|
281
|
+
JSObject ret = new JSObject();
|
|
282
|
+
ret.put("printers", printers);
|
|
283
|
+
call.resolve(ret);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
285
286
|
|
|
286
|
-
if (
|
|
287
|
-
|
|
287
|
+
if (!bluetoothAdapter.isEnabled()) {
|
|
288
|
+
Log.e(TAG, "Bluetooth is not enabled");
|
|
289
|
+
call.reject("Bluetooth is not enabled. Please turn on Bluetooth.");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
|
|
294
|
+
Log.d(TAG, "Found " + (bondedDevices != null ? bondedDevices.size() : 0) + " paired Bluetooth devices");
|
|
295
|
+
|
|
296
|
+
if (bondedDevices != null) {
|
|
297
|
+
for (BluetoothDevice device : bondedDevices) {
|
|
288
298
|
try {
|
|
289
|
-
|
|
290
|
-
String
|
|
291
|
-
String address = printer.getDevice().getAddress();
|
|
299
|
+
String name = device.getName();
|
|
300
|
+
String address = device.getAddress();
|
|
292
301
|
|
|
293
|
-
// Handle null/empty names
|
|
294
302
|
if (name == null || name.isEmpty()) {
|
|
295
303
|
name = "Unknown Device";
|
|
296
304
|
}
|
|
305
|
+
if (address == null) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
297
308
|
|
|
309
|
+
JSObject printerObj = new JSObject();
|
|
298
310
|
printerObj.put("name", name);
|
|
299
|
-
printerObj.put("address", address
|
|
311
|
+
printerObj.put("address", address);
|
|
300
312
|
printers.put(printerObj);
|
|
301
|
-
|
|
313
|
+
addedAddresses.add(address);
|
|
302
314
|
|
|
303
|
-
Log.d(TAG, "
|
|
315
|
+
Log.d(TAG, "Paired device: " + name + " (" + address + ")");
|
|
304
316
|
} catch (Exception e) {
|
|
305
|
-
Log.e(TAG, "Error processing
|
|
317
|
+
Log.e(TAG, "Error processing paired device: " + e.getMessage());
|
|
306
318
|
}
|
|
307
319
|
}
|
|
308
|
-
} else {
|
|
309
|
-
Log.d(TAG, "No paired Bluetooth printers found. Starting discovery...");
|
|
310
320
|
}
|
|
311
321
|
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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";
|
|
322
|
+
// Method 2: Also try the ESCPOS library (may find additional devices)
|
|
323
|
+
try {
|
|
324
|
+
BluetoothConnection[] escposPrinters = (new BluetoothPrintersConnections()).getList();
|
|
325
|
+
if (escposPrinters != null) {
|
|
326
|
+
for (BluetoothConnection printer : escposPrinters) {
|
|
327
|
+
String address = printer.getDevice().getAddress();
|
|
328
|
+
if (address != null && !addedAddresses.contains(address)) {
|
|
329
|
+
String name = printer.getDevice().getName();
|
|
330
|
+
if (name == null || name.isEmpty()) {
|
|
331
|
+
name = "Unknown Device";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
JSObject printerObj = new JSObject();
|
|
335
|
+
printerObj.put("name", name);
|
|
336
|
+
printerObj.put("address", address);
|
|
337
|
+
printers.put(printerObj);
|
|
338
|
+
addedAddresses.add(address);
|
|
339
|
+
|
|
340
|
+
Log.d(TAG, "ESCPOS printer: " + name + " (" + address + ")");
|
|
341
|
+
}
|
|
371
342
|
}
|
|
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
343
|
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Unregister receiver
|
|
382
|
-
try {
|
|
383
|
-
getContext().unregisterReceiver(discoveryReceiver);
|
|
384
344
|
} catch (Exception e) {
|
|
385
|
-
Log.
|
|
345
|
+
Log.d(TAG, "ESCPOS library scan: " + e.getMessage());
|
|
386
346
|
}
|
|
387
347
|
|
|
388
|
-
|
|
348
|
+
Log.d(TAG, "Total printers found: " + printers.length());
|
|
349
|
+
|
|
389
350
|
JSObject ret = new JSObject();
|
|
390
|
-
ret.put("printers",
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
discoveryReceiver = null;
|
|
351
|
+
ret.put("printers", printers);
|
|
352
|
+
call.resolve(ret);
|
|
353
|
+
} catch (Exception e) {
|
|
354
|
+
Log.e(TAG, "Error listing Bluetooth printers: " + e.getMessage(), e);
|
|
355
|
+
call.reject("Error listing Bluetooth printers: " + e.getMessage());
|
|
396
356
|
}
|
|
397
357
|
}
|
|
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
|
-
}
|
|
358
|
+
);
|
|
437
359
|
}
|
|
438
360
|
|
|
439
361
|
@PluginMethod
|
package/dist/docs.json
CHANGED
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"name": "listBluetoothPrinters",
|
|
56
|
-
"signature": "() => Promise<{ name: string; address: string; }[]>",
|
|
56
|
+
"signature": "() => Promise<{ printers: { name: string; address: string; }[]; }>",
|
|
57
57
|
"parameters": [],
|
|
58
|
-
"returns": "Promise<{ name: string; address: string; }[]>",
|
|
58
|
+
"returns": "Promise<{ printers: { name: string; address: string; }[]; }>",
|
|
59
59
|
"tags": [],
|
|
60
60
|
"docs": "Get list of paired Bluetooth printers",
|
|
61
61
|
"complexTypes": [],
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"name": "listUsbPrinters",
|
|
66
|
-
"signature": "() => Promise<{ name: string; address: string; }[]>",
|
|
66
|
+
"signature": "() => Promise<{ printers: { name: string; address: string; }[]; }>",
|
|
67
67
|
"parameters": [],
|
|
68
|
-
"returns": "Promise<{ name: string; address: string; }[]>",
|
|
68
|
+
"returns": "Promise<{ printers: { name: string; address: string; }[]; }>",
|
|
69
69
|
"tags": [],
|
|
70
70
|
"docs": "Get list of connected USB printers",
|
|
71
71
|
"complexTypes": [],
|
|
@@ -13,16 +13,20 @@ export interface ThermalPrinterPlugin {
|
|
|
13
13
|
* Get list of paired Bluetooth printers
|
|
14
14
|
*/
|
|
15
15
|
listBluetoothPrinters(): Promise<{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
printers: {
|
|
17
|
+
name: string;
|
|
18
|
+
address: string;
|
|
19
|
+
}[];
|
|
20
|
+
}>;
|
|
19
21
|
/**
|
|
20
22
|
* Get list of connected USB printers
|
|
21
23
|
*/
|
|
22
24
|
listUsbPrinters(): Promise<{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
printers: {
|
|
26
|
+
name: string;
|
|
27
|
+
address: string;
|
|
28
|
+
}[];
|
|
29
|
+
}>;
|
|
26
30
|
}
|
|
27
31
|
export interface PrintObject {
|
|
28
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface ThermalPrinterPlugin {\n /**\n * Print text using USB connection\n * @param printObject Object containing text to print\n */\n printByUsb(printObject: PrintObject): Promise<void>;\n\n /**\n * Print text using Bluetooth connection\n * @param printObject Object containing text to print\n */\n printByBluetooth(printObject: PrintObject): Promise<void>;\n\n /**\n * Get list of paired Bluetooth printers\n */\n listBluetoothPrinters(): Promise<{ name: string; address: string }[]>;\n\n /**\n * Get list of connected USB printers\n */\n listUsbPrinters(): Promise<{ name: string; address: string }[]>;\n}\n\nexport interface PrintObject {\n /**\n * Text to print. Supports ESC/POS formatting commands.\n * Example: \"[C]<b>Hello World</b>\\n[C]This is a test\"\n */\n textToPrint: string;\n\n /**\n * Optional Bluetooth MAC address of the printer to use.\n * Only applicable for printByBluetooth.\n */\n address?: string;\n\n /**\n * Optional USB device name of the printer to use.\n * Only applicable for printByUsb.\n */\n name?: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface ThermalPrinterPlugin {\n /**\n * Print text using USB connection\n * @param printObject Object containing text to print\n */\n printByUsb(printObject: PrintObject): Promise<void>;\n\n /**\n * Print text using Bluetooth connection\n * @param printObject Object containing text to print\n */\n printByBluetooth(printObject: PrintObject): Promise<void>;\n\n /**\n * Get list of paired Bluetooth printers\n */\n listBluetoothPrinters(): Promise<{ printers: { name: string; address: string }[] }>;\n\n /**\n * Get list of connected USB printers\n */\n listUsbPrinters(): Promise<{ printers: { name: string; address: string }[] }>;\n}\n\nexport interface PrintObject {\n /**\n * Text to print. Supports ESC/POS formatting commands.\n * Example: \"[C]<b>Hello World</b>\\n[C]This is a test\"\n */\n textToPrint: string;\n\n /**\n * Optional Bluetooth MAC address of the printer to use.\n * Only applicable for printByBluetooth.\n */\n address?: string;\n\n /**\n * Optional USB device name of the printer to use.\n * Only applicable for printByUsb.\n */\n name?: string;\n}\n"]}
|
|
@@ -286,26 +286,32 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
|
|
|
286
286
|
print("ThermalPrinter: listBluetoothPrinters called")
|
|
287
287
|
var printers: [[String: String]] = []
|
|
288
288
|
|
|
289
|
-
//
|
|
289
|
+
// Method 1: Get MFi/ExternalAccessory printers (classic Bluetooth printers)
|
|
290
|
+
// Most thermal printers use classic Bluetooth, which on iOS requires
|
|
291
|
+
// ExternalAccessory framework + MFi certification
|
|
290
292
|
let manager = EAAccessoryManager.shared()
|
|
291
|
-
let
|
|
293
|
+
let allAccessories = manager.connectedAccessories
|
|
292
294
|
|
|
293
|
-
print("ThermalPrinter: Found \(
|
|
295
|
+
print("ThermalPrinter: Found \(allAccessories.count) total ExternalAccessory devices")
|
|
294
296
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
297
|
+
// Return ALL connected accessories (not just those matching printer protocols)
|
|
298
|
+
// because many printers use custom protocol strings
|
|
299
|
+
for accessory in allAccessories {
|
|
300
|
+
let name = accessory.name
|
|
301
|
+
let address = accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber
|
|
302
|
+
let protocols = accessory.protocolStrings.joined(separator: ", ")
|
|
303
|
+
|
|
304
|
+
print("ThermalPrinter: Accessory: \(name), protocols: [\(protocols)]")
|
|
305
|
+
|
|
306
|
+
printers.append([
|
|
307
|
+
"name": name,
|
|
308
|
+
"address": address,
|
|
309
|
+
])
|
|
304
310
|
}
|
|
305
|
-
printers.append(contentsOf: mfiPrinters)
|
|
306
|
-
print("ThermalPrinter: Found \(mfiPrinters.count) MFi printers")
|
|
307
311
|
|
|
308
|
-
|
|
312
|
+
print("ThermalPrinter: Found \(printers.count) ExternalAccessory printers")
|
|
313
|
+
|
|
314
|
+
// Method 2: Also scan for BLE printers (some thermal printers support BLE)
|
|
309
315
|
discoveredPeripherals.removeAll()
|
|
310
316
|
listPrintersCall = call
|
|
311
317
|
|
|
@@ -323,69 +329,59 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
|
|
|
323
329
|
|
|
324
330
|
print("ThermalPrinter: Bluetooth state: \(centralManager.state.rawValue)")
|
|
325
331
|
|
|
326
|
-
// Check Bluetooth state
|
|
327
332
|
switch centralManager.state {
|
|
328
333
|
case .poweredOn:
|
|
329
|
-
|
|
330
|
-
print("ThermalPrinter: Bluetooth powered on, starting scan")
|
|
334
|
+
print("ThermalPrinter: Bluetooth powered on, starting BLE scan")
|
|
331
335
|
startBLEScan(for: call, existingPrinters: printers)
|
|
332
336
|
case .poweredOff:
|
|
333
|
-
print("ThermalPrinter: Bluetooth powered off
|
|
337
|
+
print("ThermalPrinter: Bluetooth powered off")
|
|
338
|
+
// Still return any MFi printers we found
|
|
334
339
|
call.resolve(["printers": printers])
|
|
340
|
+
listPrintersCall = nil
|
|
335
341
|
case .unauthorized:
|
|
336
342
|
print("ThermalPrinter: Bluetooth unauthorized")
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
call.reject("Bluetooth is not supported on this device.")
|
|
343
|
+
// Still return any MFi printers, but warn about BLE
|
|
344
|
+
call.resolve(["printers": printers])
|
|
345
|
+
listPrintersCall = nil
|
|
341
346
|
case .resetting, .unknown:
|
|
342
347
|
print("ThermalPrinter: Bluetooth state unknown/resetting, waiting for update")
|
|
343
|
-
// Wait for state update
|
|
344
|
-
// The state will be updated via centralManagerDidUpdateState
|
|
345
348
|
break
|
|
346
|
-
|
|
347
|
-
print("ThermalPrinter: Unknown Bluetooth state, returning \(printers.count) printers")
|
|
349
|
+
default:
|
|
348
350
|
call.resolve(["printers": printers])
|
|
351
|
+
listPrintersCall = nil
|
|
349
352
|
}
|
|
350
353
|
}
|
|
351
354
|
|
|
352
355
|
private func startBLEScan(for call: CAPPluginCall, existingPrinters: [[String: String]]) {
|
|
353
|
-
guard let centralManager = centralManager else {
|
|
354
|
-
call.resolve(["printers": existingPrinters])
|
|
355
|
-
return
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
guard centralManager.state == .poweredOn else {
|
|
356
|
+
guard let centralManager = centralManager, centralManager.state == .poweredOn else {
|
|
359
357
|
call.resolve(["printers": existingPrinters])
|
|
358
|
+
listPrintersCall = nil
|
|
360
359
|
return
|
|
361
360
|
}
|
|
362
361
|
|
|
363
|
-
// Clear previous discoveries
|
|
364
362
|
discoveredPeripherals.removeAll()
|
|
365
363
|
|
|
366
|
-
// Scan for all peripherals
|
|
367
364
|
print("ThermalPrinter: Starting BLE scan...")
|
|
368
365
|
centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
|
|
369
366
|
|
|
370
|
-
//
|
|
371
|
-
DispatchQueue.main.asyncAfter(deadline: .now() +
|
|
367
|
+
// Scan for 4 seconds (shorter — BLE devices respond quickly)
|
|
368
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
|
|
372
369
|
centralManager.stopScan()
|
|
373
370
|
print("ThermalPrinter: Stopped BLE scan. Found \(self.discoveredPeripherals.count) peripherals")
|
|
374
371
|
|
|
375
372
|
var printers = existingPrinters
|
|
376
373
|
|
|
377
|
-
// Add discovered BLE printers - return all devices, even without names
|
|
378
374
|
let blePrinters: [[String: String]] = self.discoveredPeripherals.compactMap { peripheral in
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
375
|
+
guard let name = peripheral.name, !name.isEmpty else {
|
|
376
|
+
return nil
|
|
377
|
+
}
|
|
382
378
|
return [
|
|
383
379
|
"name": name,
|
|
384
380
|
"address": peripheral.identifier.uuidString,
|
|
385
381
|
]
|
|
386
382
|
}
|
|
387
383
|
|
|
388
|
-
print("ThermalPrinter: Returning \(blePrinters.count) BLE printers")
|
|
384
|
+
print("ThermalPrinter: Returning \(blePrinters.count) BLE printers + \(existingPrinters.count) MFi printers")
|
|
389
385
|
printers.append(contentsOf: blePrinters)
|
|
390
386
|
call.resolve(["printers": printers])
|
|
391
387
|
self.listPrintersCall = nil
|
|
@@ -399,36 +395,25 @@ public class ThermalPrinterPlugin: CAPPlugin, CBCentralManagerDelegate, CBPeriph
|
|
|
399
395
|
|
|
400
396
|
// If we're waiting to list printers, start scanning now
|
|
401
397
|
if let call = listPrintersCall {
|
|
398
|
+
// Get MFi printers
|
|
399
|
+
var mfiPrinters: [[String: String]] = []
|
|
400
|
+
let manager = EAAccessoryManager.shared()
|
|
401
|
+
for accessory in manager.connectedAccessories {
|
|
402
|
+
mfiPrinters.append([
|
|
403
|
+
"name": accessory.name,
|
|
404
|
+
"address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
|
|
405
|
+
])
|
|
406
|
+
}
|
|
407
|
+
|
|
402
408
|
switch central.state {
|
|
403
409
|
case .poweredOn:
|
|
404
|
-
print("ThermalPrinter: Bluetooth now powered on, starting scan")
|
|
405
|
-
// Get existing MFi printers first
|
|
406
|
-
let manager = EAAccessoryManager.shared()
|
|
407
|
-
let accessories = manager.connectedAccessories
|
|
408
|
-
let mfiPrinters: [[String: String]] = accessories.compactMap { accessory in
|
|
409
|
-
guard accessory.protocolStrings.contains(where: { supportedPrinterProtocols.contains($0) }) else {
|
|
410
|
-
return nil
|
|
411
|
-
}
|
|
412
|
-
return [
|
|
413
|
-
"name": accessory.name,
|
|
414
|
-
"address": accessory.serialNumber.isEmpty ? "\(accessory.connectionID)" : accessory.serialNumber,
|
|
415
|
-
]
|
|
416
|
-
}
|
|
410
|
+
print("ThermalPrinter: Bluetooth now powered on, starting BLE scan")
|
|
417
411
|
startBLEScan(for: call, existingPrinters: mfiPrinters)
|
|
418
|
-
case .poweredOff:
|
|
419
|
-
print("ThermalPrinter: Bluetooth
|
|
420
|
-
call.resolve(["printers":
|
|
421
|
-
listPrintersCall = nil
|
|
422
|
-
case .unauthorized:
|
|
423
|
-
print("ThermalPrinter: Bluetooth unauthorized")
|
|
424
|
-
call.reject("Bluetooth access denied. Please enable Bluetooth permissions in Settings.")
|
|
425
|
-
listPrintersCall = nil
|
|
426
|
-
case .unsupported:
|
|
427
|
-
print("ThermalPrinter: Bluetooth unsupported")
|
|
428
|
-
call.reject("Bluetooth is not supported on this device.")
|
|
412
|
+
case .poweredOff, .unauthorized, .unsupported:
|
|
413
|
+
print("ThermalPrinter: Bluetooth not available (state: \(central.state.rawValue))")
|
|
414
|
+
call.resolve(["printers": mfiPrinters])
|
|
429
415
|
listPrintersCall = nil
|
|
430
416
|
default:
|
|
431
|
-
print("ThermalPrinter: Bluetooth state: \(central.state.rawValue)")
|
|
432
417
|
break
|
|
433
418
|
}
|
|
434
419
|
}
|