@rdlabo/capacitor-brotherprint 8.1.1 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,7 @@ import com.brother.sdk.lmprinter.PrintError
23
23
  import com.brother.sdk.lmprinter.PrinterDriverGenerator
24
24
  import com.brother.sdk.lmprinter.PrinterModel
25
25
  import com.brother.sdk.lmprinter.PrinterSearcher
26
+ import com.brother.sdk.lmprinter.PrinterSearchError
26
27
  import com.brother.sdk.lmprinter.PrinterSearcher.cancelNetworkSearch
27
28
  import com.brother.sdk.lmprinter.setting.PrintSettings
28
29
  import com.brother.sdk.lmprinter.setting.QLPrintSettings
@@ -45,8 +46,6 @@ import jp.rdlabo.capacitor.plugin.brotherprint.models.BrotherPrintSettings
45
46
  Permission(
46
47
  alias = "bluetooth",
47
48
  strings = [
48
- Manifest.permission.BLUETOOTH,
49
- Manifest.permission.BLUETOOTH_ADMIN,
50
49
  Manifest.permission.BLUETOOTH_CONNECT,
51
50
  Manifest.permission.BLUETOOTH_SCAN,
52
51
  ],
@@ -69,17 +68,36 @@ class BrotherPrint : Plugin() {
69
68
  "Unable to do call operation, user denied permission request"
70
69
 
71
70
  private var storeCall: PluginCall? = null
71
+ private var usbReceiverRegistered = false
72
72
 
73
73
  @PluginMethod
74
74
  fun printImage(call: PluginCall) {
75
75
  val encodedImage = call.getString("encodedImage", "")
76
76
  if (encodedImage == "") {
77
+ notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
78
+ JSObject().put("code", 0).put("message", "Error - Image data is not found.")
79
+ )
77
80
  call.reject("Error - Image data is not found.")
78
81
  return
79
82
  }
80
83
 
81
- val decodedString = Base64.decode(encodedImage, Base64.DEFAULT)
84
+ val decodedString = try {
85
+ Base64.decode(encodedImage, Base64.DEFAULT)
86
+ } catch (error: IllegalArgumentException) {
87
+ notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
88
+ JSObject().put("code", 0).put("message", "Error - Invalid Base64 image data")
89
+ )
90
+ call.reject("Error - Invalid Base64 image data")
91
+ return
92
+ }
82
93
  val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
94
+ if (decodedByte == null) {
95
+ notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
96
+ JSObject().put("code", 0).put("message", "Error - Create decodedByte From ImageData is failed.")
97
+ )
98
+ call.reject("Error - Create decodedByte From ImageData is failed.")
99
+ return
100
+ }
83
101
 
84
102
  val port: String? = call.getString("port", "wifi")
85
103
  val channelInfo: String? = call.getString("channelInfo", "")
@@ -100,6 +118,9 @@ class BrotherPrint : Plugin() {
100
118
  settings = BrotherPrintSettings().modelTDSettings(call, settings)
101
119
  settings.workPath = bridge.context.cacheDir.path;
102
120
  } else {
121
+ notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
122
+ JSObject().put("code", 0).put("message", "Error - modelName:$modelName is not supported")
123
+ )
103
124
  call.reject("Error - modelName:$modelName is not supported")
104
125
  return;
105
126
  }
@@ -113,6 +134,9 @@ class BrotherPrint : Plugin() {
113
134
  channelInfo, bridge.context, getBluetoothAdapter(bridge.context)
114
135
  )
115
136
  else -> {
137
+ notifyListeners(BrotherPrintEvent.onPrintError.webEventName,
138
+ JSObject().put("code", 0).put("message", "Error - port:$port is not supported")
139
+ )
116
140
  call.reject("Error - port:$port is not supported")
117
141
  return@Thread
118
142
  }
@@ -161,6 +185,10 @@ class BrotherPrint : Plugin() {
161
185
 
162
186
  @PluginMethod
163
187
  fun isChannelAvailable(call: PluginCall) {
188
+ if (call.getString("port") in listOf("bluetooth", "bluetoothLowEnergy") && !isBluetoothPermissionGranted()) {
189
+ call.resolve(JSObject().put("result", false))
190
+ return
191
+ }
164
192
  val port: String? = call.getString("port", "wifi")
165
193
  val channelInfo: String? = call.getString("channelInfo", "")
166
194
 
@@ -200,29 +228,22 @@ class BrotherPrint : Plugin() {
200
228
  }
201
229
  }
202
230
 
231
+ @Synchronized
203
232
  private fun searchUsbPrinter(call: PluginCall) {
233
+ if (this.storeCall != null) {
234
+ call.reject("Error - USB permission request is already pending")
235
+ return
236
+ }
204
237
  if (!this.requestUsbPermission(call)) {
205
- this.storeCall = call;
206
238
  return
207
239
  }
208
- this.storeCall = null
209
240
 
210
241
  Thread {
211
242
  val result = PrinterSearcher.startUSBSearch(bridge.context)
212
243
 
213
- when (result.error.code) {
214
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.NoError -> {
215
- }
216
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.NotPermitted -> {
217
- // TODO: has error
218
- }
219
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.Canceled,
220
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.InterfaceInactive,
221
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.InterfaceUnsupported,
222
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.AlreadySearching,
223
- com.brother.sdk.lmprinter.PrinterSearchError.ErrorCode.UnknownError -> {
224
- }
225
- null -> {}
244
+ if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
245
+ call.reject("Error - startUSBSearch: " + result.error.code.toString())
246
+ return@Thread
226
247
  }
227
248
 
228
249
  for (channel in result.channels){
@@ -231,8 +252,8 @@ class BrotherPrint : Plugin() {
231
252
  this.chanelToPrinter("usb", channel)
232
253
  );
233
254
  }
255
+ call.resolve();
234
256
  }.start()
235
- call.resolve();
236
257
  }
237
258
 
238
259
  private fun searchWiFiPrinter(call: PluginCall) {
@@ -243,7 +264,7 @@ class BrotherPrint : Plugin() {
243
264
  }
244
265
  val intDuration: Int = call.getInt("searchDuration") ?: 15 ;
245
266
  val option = NetworkSearchOption(intDuration.toDouble(), false);
246
- PrinterSearcher.startNetworkSearch(bridge.context, option){ channel ->
267
+ val result = PrinterSearcher.startNetworkSearch(bridge.context, option){ channel ->
247
268
  run {
248
269
  Log.d("brother", this.chanelToPrinter("wifi", channel).toString())
249
270
  this.notifyListeners(
@@ -253,8 +274,12 @@ class BrotherPrint : Plugin() {
253
274
  }
254
275
  }
255
276
  this.cancelRoutineWiFi = null
277
+ if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
278
+ call.reject("Error - startNetworkSearch: " + result.error.code.toString())
279
+ return@Thread
280
+ }
281
+ call.resolve();
256
282
  }.start()
257
- call.resolve();
258
283
  }
259
284
 
260
285
  private fun checkBLEChannel(call: PluginCall) {
@@ -263,12 +288,21 @@ class BrotherPrint : Plugin() {
263
288
  } else {
264
289
  Log.d("brother", "checkBLEChannel")
265
290
  Thread {
266
- for (channel in PrinterSearcher.startBluetoothSearch(bridge.context).channels){
291
+ val result = PrinterSearcher.startBluetoothSearch(bridge.context)
292
+ if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
293
+ call.reject("Error - startBluetoothSearch: " + result.error.code.toString())
294
+ return@Thread
295
+ }
296
+ for (channel in result.channels){
297
+ if (!matchesBluetoothPrinterFilter(call.getBoolean("bluetoothPrintersOnly", false) == true) {
298
+ val device = getBluetoothAdapter(bridge.context)?.getRemoteDevice(channel.channelInfo)
299
+ device?.bluetoothClass?.deviceClass
300
+ }) continue
267
301
  Log.d("brother", this.chanelToPrinter("bluetooth", channel).toString())
268
302
  this.notifyListeners(BrotherPrintEvent.onPrinterAvailable.webEventName, this.chanelToPrinter("bluetooth", channel));
269
303
  }
304
+ call.resolve();
270
305
  }.start()
271
- call.resolve();
272
306
  }
273
307
  }
274
308
 
@@ -276,18 +310,18 @@ class BrotherPrint : Plugin() {
276
310
  if (!isBluetoothPermissionGranted()) {
277
311
  requestPermissionForAlias("bluetooth", call, "permissionCallback");
278
312
  return;
279
- } else if (!isLocationPermissionGranted()) {
280
- requestPermissionForAlias("location", call, "permissionCallback");
313
+ } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && !isLocationPermissionGranted()) {
314
+ requestPermissionForAlias("location", call, "locationPermissionCallback");
281
315
  return;
282
316
  } else {
283
317
  Log.d("brother", "searchBLEPrinter")
284
318
  Thread {
285
319
  this.cancelRoutineBluetooth = {
286
- cancelNetworkSearch()
320
+ PrinterSearcher.cancelBLESearch()
287
321
  }
288
322
  val intDuration: Int = call.getInt("searchDuration") ?: 15 ;
289
323
  val option = BLESearchOption(intDuration.toDouble())
290
- PrinterSearcher.startBLESearch(bridge.context, option){ channel ->
324
+ val result = PrinterSearcher.startBLESearch(bridge.context, option){ channel ->
291
325
  run {
292
326
  Log.d("brother", this.chanelToPrinter("bluetoothLowEnergy", channel).toString())
293
327
  this.notifyListeners(
@@ -297,15 +331,19 @@ class BrotherPrint : Plugin() {
297
331
  }
298
332
  }
299
333
  this.cancelRoutineBluetooth = null;
334
+ if (result.error.code != PrinterSearchError.ErrorCode.NoError) {
335
+ call.reject("Error - startBLESearch: " + result.error.code.toString())
336
+ return@Thread
337
+ }
338
+ call.resolve();
300
339
  }.start()
301
- call.resolve();
302
340
  }
303
341
  }
304
342
 
305
343
  private fun chanelToPrinter(port: String, channel: Channel): JSObject? {
306
344
  Log.d("brother", channel.toString());
307
345
  val modelName = channel.extraInfo[Channel.ExtraInfoKey.ModelName] ?: ""
308
- val serialNumber = channel.extraInfo[Channel.ExtraInfoKey.SerialNubmer] ?: ""
346
+ val serialNumber = channel.extraInfo[Channel.ExtraInfoKey.SerialNumber] ?: ""
309
347
  val macAddress = channel.extraInfo[Channel.ExtraInfoKey.MACAddress] ?: ""
310
348
  val nodeName = channel.extraInfo[Channel.ExtraInfoKey.NodeName] ?: ""
311
349
  val location = channel.extraInfo[Channel.ExtraInfoKey.Location] ?: ""
@@ -346,49 +384,37 @@ class BrotherPrint : Plugin() {
346
384
  call.reject(PERMISSION_DENIED_ERROR)
347
385
  return
348
386
  }
387
+ when (call.methodName) {
388
+ "search" -> this.search(call)
389
+ }
390
+ }
391
+
392
+ @PermissionCallback
393
+ private fun locationPermissionCallback(call: PluginCall) {
349
394
  if (!isLocationPermissionGranted()) {
350
395
  Log.d("brother", "!isLocationPermissionGranted()")
351
396
  call.reject(PERMISSION_DENIED_ERROR)
352
397
  return
353
398
  }
354
- when (call.methodName) {
355
- "search" -> this.search(call)
356
- }
399
+ this.search(call)
357
400
  }
358
401
 
359
- /**
360
- * TODO: This is not called for in spite of registration.
361
- * Therefore, it is now necessary for the user to run it again after permission is granted.
362
- */
363
402
  private val usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
364
403
  override fun onReceive(context: Context?, intent: Intent?) {
365
- if (intent?.action == ActionUSBPermission && intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
366
- storeCall?.let { searchUsbPrinter(it) }
367
- } else {
368
- storeCall?.reject("Error - usbReceiver can't current receiver");
404
+ synchronized(this@BrotherPrint) {
405
+ if (intent?.action != ActionUSBPermission) return
406
+ val call = storeCall ?: return
407
+ storeCall = null
408
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
409
+ searchUsbPrinter(call)
410
+ } else {
411
+ call.reject("Error - usbReceiver can't current receiver");
412
+ }
369
413
  }
370
414
  }
371
415
  }
372
416
 
373
417
  private fun requestUsbPermission(call: PluginCall): Boolean {
374
- val permissionIntent = PendingIntent.getBroadcast(
375
- bridge.context, 0, Intent(ActionUSBPermission), PendingIntent.FLAG_IMMUTABLE
376
- )
377
-
378
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
379
- bridge.context.registerReceiver(
380
- usbReceiver, IntentFilter(ActionUSBPermission),
381
- Context.RECEIVER_NOT_EXPORTED
382
- )
383
- } else {
384
- ContextCompat.registerReceiver(
385
- bridge.context,
386
- usbReceiver,
387
- IntentFilter(ActionUSBPermission),
388
- ContextCompat.RECEIVER_NOT_EXPORTED
389
- )
390
- }
391
-
392
418
  var connectDevice: UsbDevice? = null
393
419
  val usbManager = bridge.context.getSystemService(Context.USB_SERVICE) as UsbManager
394
420
  for (device in usbManager.deviceList.values) {
@@ -399,14 +425,48 @@ class BrotherPrint : Plugin() {
399
425
  call.reject("Error - connection failed: device not found")
400
426
  return false
401
427
  }
428
+ if (usbManager.hasPermission(connectDevice)) return true
429
+
430
+ val permissionIntent = PendingIntent.getBroadcast(
431
+ bridge.context, 0, Intent(ActionUSBPermission), PendingIntent.FLAG_IMMUTABLE
432
+ )
433
+
434
+ if (!usbReceiverRegistered) {
435
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
436
+ bridge.context.registerReceiver(
437
+ usbReceiver, IntentFilter(ActionUSBPermission),
438
+ Context.RECEIVER_NOT_EXPORTED
439
+ )
440
+ } else {
441
+ ContextCompat.registerReceiver(
442
+ bridge.context,
443
+ usbReceiver,
444
+ IntentFilter(ActionUSBPermission),
445
+ ContextCompat.RECEIVER_NOT_EXPORTED
446
+ )
447
+ }
448
+ usbReceiverRegistered = true
449
+ }
402
450
 
451
+ storeCall = call
403
452
  usbManager.requestPermission(connectDevice, permissionIntent)
453
+ return false
454
+ }
404
455
 
405
- return usbManager.hasPermission(connectDevice)
456
+ @Synchronized
457
+ override fun handleOnDestroy() {
458
+ val call = storeCall
459
+ storeCall = null
460
+ call?.reject("Error - plugin destroyed while waiting for USB permission")
461
+ if (usbReceiverRegistered) {
462
+ bridge.context.unregisterReceiver(usbReceiver)
463
+ usbReceiverRegistered = false
464
+ }
465
+ super.handleOnDestroy()
406
466
  }
407
467
 
408
468
  private fun isBluetoothPermissionGranted(): Boolean {
409
- return getPermissionState("bluetooth") == PermissionState.GRANTED
469
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || getPermissionState("bluetooth") == PermissionState.GRANTED
410
470
  }
411
471
 
412
472
  private fun isLocationPermissionGranted(): Boolean {
package/dist/docs.json CHANGED
@@ -66,7 +66,7 @@
66
66
  "parameters": [],
67
67
  "returns": "Promise<void>",
68
68
  "tags": [],
69
- "docs": "Basically, it times out, so there is no need to use it. Use it when you want to run multiple connectType searches at the same time and time out any of them manually.",
69
+ "docs": "Stop an active search before its timeout, including when leaving the screen.",
70
70
  "complexTypes": [],
71
71
  "slug": "cancelsearchwifiprinter"
72
72
  },
@@ -76,7 +76,7 @@
76
76
  "parameters": [],
77
77
  "returns": "Promise<void>",
78
78
  "tags": [],
79
- "docs": "Basically, it times out, so there is no need to use it. Use it when you want to run multiple connectType searches at the same time and time out any of them manually.",
79
+ "docs": "Stop an active search before its timeout, including when leaving the screen.",
80
80
  "complexTypes": [],
81
81
  "slug": "cancelsearchbluetoothprinter"
82
82
  },
@@ -929,7 +929,7 @@
929
929
  "docs": "",
930
930
  "types": [
931
931
  {
932
- "text": "{\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n}",
932
+ "text": "{\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n /**\n * Android Bluetooth Classic only. Include only devices whose Bluetooth class\n * reports a printer. Defaults to false; ignored for other ports and on iOS.\n * This does not identify Brother devices. Devices with an unknown class are excluded when true.\n */\n bluetoothPrintersOnly?: boolean;\n}",
933
933
  "complexTypes": [
934
934
  "BRLMPrinterPort"
935
935
  ]
@@ -13,11 +13,11 @@ export interface BrotherPrintPlugin {
13
13
  */
14
14
  isChannelAvailable(option: BRLMChannelResult): Promise<isChannelAvailableResult>;
15
15
  /**
16
- * Basically, it times out, so there is no need to use it. Use it when you want to run multiple connectType searches at the same time and time out any of them manually.
16
+ * Stop an active search before its timeout, including when leaving the screen.
17
17
  */
18
18
  cancelSearchWiFiPrinter(): Promise<void>;
19
19
  /**
20
- * Basically, it times out, so there is no need to use it. Use it when you want to run multiple connectType searches at the same time and time out any of them manually.
20
+ * Stop an active search before its timeout, including when leaving the screen.
21
21
  */
22
22
  cancelSearchBluetoothPrinter(): Promise<void>;
23
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\nimport type { BrotherPrintEventsEnum } from './events.enum';\nimport type {\n BRLMChannelResult,\n BRLMPrintOptions,\n BRLMSearchOption,\n ErrorInfo,\n isChannelAvailableResult,\n} from './interfaces';\n\nexport interface BrotherPrintPlugin {\n printImage(options: BRLMPrintOptions): Promise<void>;\n\n /**\n * Search for printers. If not found, it will return an empty array.(not error)\n */\n search(option: BRLMSearchOption): Promise<void>;\n\n /**\n * If you have saved the last connected BRLMChannelResult,\n * you can use it to verify whether it is currently usable.\n */\n isChannelAvailable(option: BRLMChannelResult): Promise<isChannelAvailableResult>;\n\n /**\n * Basically, it times out, so there is no need to use it. Use it when you want to run multiple connectType searches at the same time and time out any of them manually.\n */\n cancelSearchWiFiPrinter(): Promise<void>;\n\n /**\n * Basically, it times out, so there is no need to use it. Use it when you want to run multiple connectType searches at the same time and time out any of them manually.\n */\n cancelSearchBluetoothPrinter(): Promise<void>;\n\n /**\n * Find the printer that can connected to the device.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrinterAvailable,\n listenerFunc: (printers: BRLMChannelResult) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Success Print Event\n */\n addListener(eventName: BrotherPrintEventsEnum.onPrint, listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Failed to connect to the printer.\n * ex: Bluetooth is off, Printer is off, etc.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrintFailedCommunication,\n listenerFunc: (info: ErrorInfo) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Failed to print.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrintError,\n listenerFunc: (info: ErrorInfo) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\nimport type { BrotherPrintEventsEnum } from './events.enum';\nimport type {\n BRLMChannelResult,\n BRLMPrintOptions,\n BRLMSearchOption,\n ErrorInfo,\n isChannelAvailableResult,\n} from './interfaces';\n\nexport interface BrotherPrintPlugin {\n printImage(options: BRLMPrintOptions): Promise<void>;\n\n /**\n * Search for printers. If not found, it will return an empty array.(not error)\n */\n search(option: BRLMSearchOption): Promise<void>;\n\n /**\n * If you have saved the last connected BRLMChannelResult,\n * you can use it to verify whether it is currently usable.\n */\n isChannelAvailable(option: BRLMChannelResult): Promise<isChannelAvailableResult>;\n\n /**\n * Stop an active search before its timeout, including when leaving the screen.\n */\n cancelSearchWiFiPrinter(): Promise<void>;\n\n /**\n * Stop an active search before its timeout, including when leaving the screen.\n */\n cancelSearchBluetoothPrinter(): Promise<void>;\n\n /**\n * Find the printer that can connected to the device.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrinterAvailable,\n listenerFunc: (printers: BRLMChannelResult) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Success Print Event\n */\n addListener(eventName: BrotherPrintEventsEnum.onPrint, listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Failed to connect to the printer.\n * ex: Bluetooth is off, Printer is off, etc.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrintFailedCommunication,\n listenerFunc: (info: ErrorInfo) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Failed to print.\n */\n addListener(\n eventName: BrotherPrintEventsEnum.onPrintError,\n listenerFunc: (info: ErrorInfo) => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
@@ -124,6 +124,12 @@ export type BRLMSearchOption = {
124
124
  * use only port is 'wifi' or 'bluetoothLowEnergy'.
125
125
  */
126
126
  searchDuration: number;
127
+ /**
128
+ * Android Bluetooth Classic only. Include only devices whose Bluetooth class
129
+ * reports a printer. Defaults to false; ignored for other ports and on iOS.
130
+ * This does not identify Brother devices. Devices with an unknown class are excluded when true.
131
+ */
132
+ bluetoothPrintersOnly?: boolean;
127
133
  };
128
134
  export type ErrorInfo = {
129
135
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n BRLMPrinterLabelName,\n BRLMPrinterModelName,\n BRLMPrinterAutoCutType,\n BRLMPrinterCompressMode,\n BRLMPrinterHalftone,\n BRLMPrinterHalftoneThresholdType,\n BRLMPrinterHorizontalAlignment,\n BRLMPrinterNumberOfCopies,\n BRLMPrinterPrintQuality,\n BRLMPrinterImageRotation,\n BRLMPrinterScaleMode,\n BRLMPrinterScaleValueType,\n BRLMPrinterVerticalAlignment,\n BRLMPrinterCustomPaperType,\n BRLMPrinterCustomPaperUnit,\n BRLMPrinterPort,\n} from './brother-printer.enum';\n\nexport type BRLMChannelResult = {\n port: BRLMPrinterPort;\n modelName: string;\n serialNumber: string;\n macAddress: string;\n nodeName: string;\n location: string;\n\n /**\n * This need to connect to the printer.\n * wifi: IP Address\n * bluetooth: macAddress\n * bluetoothLowEnergy: modelName for bluetoothLowEnergy\n */\n channelInfo: string;\n};\n\nexport type BRLMPrintOptions = {\n encodedImage: string;\n\n /**\n * Should use enum BRLMPrinterModelName\n */\n modelName: BRLMPrinterModelName;\n} & Partial<BRLMChannelResult> &\n (BRLMPrinterQLModelSettings | BRLMPrinterTDModelSettings);\n\nexport type isChannelAvailableResult = {\n result: boolean;\n};\n\nexport type BRLMPrinterTDModelSettings = {\n /**\n * Should use enum BRKMPrinterCustomPaperType\n */\n paperType: BRLMPrinterCustomPaperType;\n\n /**\n * The width of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeWidth: number;\n\n /**\n * The length of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeLength: number;\n\n /**\n * It is the difference between a sticker and a mount.\n * For example, the RD-U04J1 is `1.0, 2.0, 1.0, 2.0`\n */\n marginTop: number;\n marginRight: number;\n marginBottom: number;\n marginLeft: number;\n\n /**\n * The spacing between seals. For example, the RD-U04J1 is 0.2.\n */\n gapLength: number;\n\n paperMarkPosition: number;\n paperMarkLength: number;\n\n /**\n * Should use enum BRKMPrinterCustomPaperUnit.\n * For example, the RD-U04J1 is mm.\n */\n paperUnit: BRLMPrinterCustomPaperUnit;\n};\n\nexport type BRLMPrinterQLModelSettings = {\n /**\n * Should use enum BRLMPrinterLabelName\n */\n labelName: BRLMPrinterLabelName;\n} & BRLMPrinterSettings;\n\n/**\n * These are optional. If these are not set, default values are assigned by the printer.\n */\nexport type BRLMPrinterSettings = {\n /**\n * The number of copies you print.\n */\n numberOfCopies?: BRLMPrinterNumberOfCopies;\n\n /**\n * Whether the auto-cut is enabled or not. If true, your printer cut the paper each page.\n */\n autoCut?: BRLMPrinterAutoCutType;\n\n /**\n * A scale mode that specifies how your data is scaled in a print area of your printer.\n */\n scaleMode?: BRLMPrinterScaleMode;\n\n /**\n * A scale value. This is effective when ScaleMode is ScaleValue.\n */\n scaleValue?: BRLMPrinterScaleValueType;\n\n /**\n * A way to rasterize your data.\n */\n halftone?: BRLMPrinterHalftone;\n\n /**\n * A threshold value. This is effective when the Halftone is Threshold.\n */\n halftoneThreshold?: BRLMPrinterHalftoneThresholdType;\n\n /**\n * An image rotation that specifies the angle in which your data is placed in the print area. Rotation direction is clockwise.\n */\n imageRotation?: BRLMPrinterImageRotation;\n\n /**\n * A vertical alignment that specifies how your data is placed in the printable area.\n */\n verticalAlignment?: BRLMPrinterVerticalAlignment;\n\n /**\n * A horizontal alignment that specifies how your data is placed in the printable area.\n */\n horizontalAlignment?: BRLMPrinterHorizontalAlignment;\n\n /**\n * A compress mode that specifies how to compress your data.\n * note: This is ios only.\n */\n compressMode?: BRLMPrinterCompressMode;\n\n /**\n * A priority that is print speed or print quality. Whether or not this has an effect is depend on your printer.\n */\n printQuality?: BRLMPrinterPrintQuality;\n};\n\nexport type BRLMSearchOption = {\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n};\n\nexport type ErrorInfo = {\n message: string;\n code: number;\n};\n"]}
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n BRLMPrinterLabelName,\n BRLMPrinterModelName,\n BRLMPrinterAutoCutType,\n BRLMPrinterCompressMode,\n BRLMPrinterHalftone,\n BRLMPrinterHalftoneThresholdType,\n BRLMPrinterHorizontalAlignment,\n BRLMPrinterNumberOfCopies,\n BRLMPrinterPrintQuality,\n BRLMPrinterImageRotation,\n BRLMPrinterScaleMode,\n BRLMPrinterScaleValueType,\n BRLMPrinterVerticalAlignment,\n BRLMPrinterCustomPaperType,\n BRLMPrinterCustomPaperUnit,\n BRLMPrinterPort,\n} from './brother-printer.enum';\n\nexport type BRLMChannelResult = {\n port: BRLMPrinterPort;\n modelName: string;\n serialNumber: string;\n macAddress: string;\n nodeName: string;\n location: string;\n\n /**\n * This need to connect to the printer.\n * wifi: IP Address\n * bluetooth: macAddress\n * bluetoothLowEnergy: modelName for bluetoothLowEnergy\n */\n channelInfo: string;\n};\n\nexport type BRLMPrintOptions = {\n encodedImage: string;\n\n /**\n * Should use enum BRLMPrinterModelName\n */\n modelName: BRLMPrinterModelName;\n} & Partial<BRLMChannelResult> &\n (BRLMPrinterQLModelSettings | BRLMPrinterTDModelSettings);\n\nexport type isChannelAvailableResult = {\n result: boolean;\n};\n\nexport type BRLMPrinterTDModelSettings = {\n /**\n * Should use enum BRKMPrinterCustomPaperType\n */\n paperType: BRLMPrinterCustomPaperType;\n\n /**\n * The width of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeWidth: number;\n\n /**\n * The length of the label. For example, the RD-U04J1 is 60.0 wide.\n */\n tapeLength: number;\n\n /**\n * It is the difference between a sticker and a mount.\n * For example, the RD-U04J1 is `1.0, 2.0, 1.0, 2.0`\n */\n marginTop: number;\n marginRight: number;\n marginBottom: number;\n marginLeft: number;\n\n /**\n * The spacing between seals. For example, the RD-U04J1 is 0.2.\n */\n gapLength: number;\n\n paperMarkPosition: number;\n paperMarkLength: number;\n\n /**\n * Should use enum BRKMPrinterCustomPaperUnit.\n * For example, the RD-U04J1 is mm.\n */\n paperUnit: BRLMPrinterCustomPaperUnit;\n};\n\nexport type BRLMPrinterQLModelSettings = {\n /**\n * Should use enum BRLMPrinterLabelName\n */\n labelName: BRLMPrinterLabelName;\n} & BRLMPrinterSettings;\n\n/**\n * These are optional. If these are not set, default values are assigned by the printer.\n */\nexport type BRLMPrinterSettings = {\n /**\n * The number of copies you print.\n */\n numberOfCopies?: BRLMPrinterNumberOfCopies;\n\n /**\n * Whether the auto-cut is enabled or not. If true, your printer cut the paper each page.\n */\n autoCut?: BRLMPrinterAutoCutType;\n\n /**\n * A scale mode that specifies how your data is scaled in a print area of your printer.\n */\n scaleMode?: BRLMPrinterScaleMode;\n\n /**\n * A scale value. This is effective when ScaleMode is ScaleValue.\n */\n scaleValue?: BRLMPrinterScaleValueType;\n\n /**\n * A way to rasterize your data.\n */\n halftone?: BRLMPrinterHalftone;\n\n /**\n * A threshold value. This is effective when the Halftone is Threshold.\n */\n halftoneThreshold?: BRLMPrinterHalftoneThresholdType;\n\n /**\n * An image rotation that specifies the angle in which your data is placed in the print area. Rotation direction is clockwise.\n */\n imageRotation?: BRLMPrinterImageRotation;\n\n /**\n * A vertical alignment that specifies how your data is placed in the printable area.\n */\n verticalAlignment?: BRLMPrinterVerticalAlignment;\n\n /**\n * A horizontal alignment that specifies how your data is placed in the printable area.\n */\n horizontalAlignment?: BRLMPrinterHorizontalAlignment;\n\n /**\n * A compress mode that specifies how to compress your data.\n * note: This is ios only.\n */\n compressMode?: BRLMPrinterCompressMode;\n\n /**\n * A priority that is print speed or print quality. Whether or not this has an effect is depend on your printer.\n */\n printQuality?: BRLMPrinterPrintQuality;\n};\n\nexport type BRLMSearchOption = {\n /**\n * 'usb' is android only, and now developing.\n */\n port: BRLMPrinterPort;\n\n /**\n * searchDuration is the time to end search for devices.\n * default is 15 seconds.\n * use only port is 'wifi' or 'bluetoothLowEnergy'.\n */\n searchDuration: number;\n /**\n * Android Bluetooth Classic only. Include only devices whose Bluetooth class\n * reports a printer. Defaults to false; ignored for other ports and on iOS.\n * This does not identify Brother devices. Devices with an unknown class are excluded when true.\n */\n bluetoothPrintersOnly?: boolean;\n};\n\nexport type ErrorInfo = {\n message: string;\n code: number;\n};\n"]}
package/docs/events.md ADDED
@@ -0,0 +1,58 @@
1
+ # Events
2
+
3
+ Listen for discovered printers and print results. Register listeners before [Search](/docs/search) and [Print](/docs/print) so the first events are not missed.
4
+
5
+ ```typescript
6
+ import type { PluginListenerHandle } from '@capacitor/core';
7
+ import { BrotherPrint, BrotherPrintEventsEnum } from '@rdlabo/capacitor-brotherprint';
8
+
9
+ const handles: PluginListenerHandle[] = [];
10
+
11
+ const registerPrintListeners = async () => {
12
+ handles.push(
13
+ await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrinterAvailable, (printer) => {
14
+ console.log('printer', printer.channelInfo);
15
+ }),
16
+ );
17
+ handles.push(
18
+ await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrint, () => {
19
+ console.log('onPrint');
20
+ }),
21
+ );
22
+ handles.push(
23
+ await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrintFailedCommunication, (info) => {
24
+ console.log('onPrintFailedCommunication', info);
25
+ }),
26
+ );
27
+ handles.push(
28
+ await BrotherPrint.addListener(BrotherPrintEventsEnum.onPrintError, (info) => {
29
+ console.log('onPrintError', info);
30
+ }),
31
+ );
32
+ };
33
+
34
+ const removePrintListeners = async () => {
35
+ await Promise.all(handles.map((handle) => handle.remove()));
36
+ };
37
+ ```
38
+
39
+ | Event | When it fires |
40
+ | ---------------------------- | ------------------------------------ |
41
+ | `onPrinterAvailable` | A printer that can connect was found |
42
+ | `onPrint` | Print succeeded |
43
+ | `onPrintFailedCommunication` | The printer could not be reached |
44
+ | `onPrintError` | Print failed |
45
+
46
+ When `printImage` rejects an invalid image, unsupported model or port, or a print-settings creation failure, it also emits `onPrintError` with `code: 0` and an explanatory `message`. This code denotes a plugin validation error, not an SDK error. Failure to open the printer channel emits `onPrintFailedCommunication` instead.
47
+
48
+ See the demo for a complete page:
49
+
50
+ https://github.com/rdlabo-dev/capacitor-brotherprint/blob/v8.1.1/demo/src/app/home/home.page.ts
51
+
52
+ <!-- !::addListener.BrotherPrintEventsEnum:: -->
53
+
54
+ <!-- !::BrotherPrintEventsEnum:: -->
55
+
56
+ <!-- !::PluginListenerHandle:: -->
57
+
58
+ <!-- !::ErrorInfo:: -->
@@ -0,0 +1,133 @@
1
+ # Installation
2
+
3
+ ## Install
4
+
5
+ ```
6
+ npm install @rdlabo/capacitor-brotherprint
7
+ ```
8
+
9
+ The published plugin package declares an SPM dependency on a **local** Brother kit at your app root:
10
+
11
+ `ios/LocalPackages/BRLMPrinterKit`
12
+
13
+ That path is relative from `node_modules/@rdlabo/capacitor-brotherprint` (`../../../ios/LocalPackages/BRLMPrinterKit`). Place the Brother iOS SDK under your Capacitor app’s `ios` tree as shown below, then run `npx cap sync`. This plugin requires **iOS 15** and **Swift Package Manager** only (no CocoaPods / Podfile steps).
14
+
15
+ This plugin does not redistribute the Brother SDK. Download it from Brother’s official pages for your platform.
16
+
17
+ ## Initialize the Brother SDK
18
+
19
+ ### Android configuration
20
+
21
+ 1. Place the following files in the android folder of your Capacitor project:
22
+
23
+ - `android/BrotherPrintLibrary/BrotherPrintLibrary.aar`
24
+ - `android/BrotherPrintLibrary/build.gradle`
25
+
26
+ Download the Android SDK from Brother: https://support.brother.co.jp/j/s/es/dev/ja/mobilesdk/android/index.html?c=jp&lang=ja&navi=offall&comple=on&redirect=on#ver4
27
+
28
+ 2. In `android/BrotherPrintLibrary/build.gradle`, include:
29
+
30
+ ```
31
+ configurations.maybeCreate("default")
32
+ artifacts.add("default", file('BrotherPrintLibrary.aar'))
33
+ ```
34
+
35
+ 3. Open `android/settings.gradle` and add:
36
+
37
+ ```
38
+ include ':BrotherPrintLibrary'
39
+ project(':BrotherPrintLibrary').projectDir = new File('./BrotherPrintLibrary/')
40
+ ```
41
+
42
+ ### iOS configuration
43
+
44
+ 1. Under your Capacitor app (not inside `node_modules`), place:
45
+
46
+ - `ios/LocalPackages/BRLMPrinterKit/Sources/BRLMPrinterKit.xcframework`
47
+ - `ios/LocalPackages/BRLMPrinterKit/Package.swift`
48
+
49
+ Download the iOS SDK from Brother: https://support.brother.com/g/s/es/dev/en/mobilesdk/ios/index.html
50
+
51
+ 2. Create `ios/LocalPackages/BRLMPrinterKit/Package.swift` for that local binary package (minimum iOS 15 to match the plugin):
52
+
53
+ ```swift
54
+ // swift-tools-version: 5.9
55
+ import PackageDescription
56
+
57
+ let package = Package(
58
+ name: "BRLMPrinterKit",
59
+ platforms: [
60
+ .iOS(.v15)
61
+ ],
62
+ products: [
63
+ .library(name: "BRLMPrinterKit", targets: ["BRLMPrinterKit"])
64
+ ],
65
+ targets: [
66
+ .binaryTarget(
67
+ name: "BRLMPrinterKit",
68
+ path: "Sources/BRLMPrinterKit.xcframework"
69
+ )
70
+ ]
71
+ )
72
+ ```
73
+
74
+ 3. After the SDK files are in place, run `npx cap sync` so the app’s iOS project picks up the plugin and the local package path.
75
+
76
+ ## Permission configuration
77
+
78
+ ### Android configuration
79
+
80
+ Update `AndroidManifest.xml` to include the following permissions:
81
+
82
+ ```diff
83
+ - <manifest xmlns:android="http://schemas.android.com/apk/res/android">
84
+ + <manifest xmlns:android="http://schemas.android.com/apk/res/android"
85
+ + xmlns:tools="http://schemas.android.com/tools">
86
+ ...
87
+ + <!-- For Bluetooth -->
88
+ + <uses-permission android:name="android.permission.BLUETOOTH" />
89
+ + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
90
+ + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
91
+
92
+ + <!-- For Bluetooth Low Energy, Android 11 and earlier-->
93
+ + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
94
+ + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
95
+
96
+ + <!-- For Bluetooth Low Energy, Android 12 and later -->
97
+ + <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
98
+ + android:usesPermissionFlags="neverForLocation"
99
+ + tools:targetApi="s" />
100
+ ```
101
+
102
+ More information: https://support.brother.co.jp/j/s/support/html/mobilesdk/guide/getting-started/getting-started-android.html
103
+
104
+ ### iOS configuration
105
+
106
+ Update `Info.plist` to include the following keys. `UISupportedExternalAccessoryProtocols` must be an **array of strings**.
107
+
108
+ ```diff
109
+ + <key>NSBluetoothAlwaysUsageDescription</key>
110
+ + <string>【Why use Bluetooth for your app.】</string>
111
+ + <key>NSBluetoothPeripheralUsageDescription</key>
112
+ + <string>【Why use Bluetooth for your app.】</string>
113
+ + <key>NSBonjourServices</key>
114
+ + <array>
115
+ + <string>_pdl-datastream._tcp</string>
116
+ + <string>_printer._tcp</string>
117
+ + <string>_ipp._tcp</string>
118
+ + </array>
119
+ + <key>NSLocalNetworkUsageDescription</key>
120
+ + <string>【Why use WiFi for your app.】</string>
121
+ + <key>UISupportedExternalAccessoryProtocols</key>
122
+ + <array>
123
+ + <string>com.brother.ptcbp</string>
124
+ + </array>
125
+ ```
126
+
127
+ #### Bluetooth plist types (verified September 9, 2026)
128
+
129
+ `UISupportedExternalAccessoryProtocols` must be an **array of strings**, even when `com.brother.ptcbp` is the only protocol. Earlier versions of this repository's example incorrectly used a single `<string>`, which caused Bluetooth discovery to crash with `-[__NSCFString count]: unrecognized selector`. Use the `<array>` shown above. See [Apple's type definition](https://developer.apple.com/documentation/bundleresources/information-property-list/uisupportedexternalaccessoryprotocols).
130
+
131
+ The `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription` values are **strings**, not arrays. The examples for these keys in [Brother's official iOS setup guide](https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/getting-started/getting-started-ios.html) are correct as of September 9, 2026; they were not the cause of this crash. Brother's guide separately instructs adding `com.brother.ptcbp` as an item under `UISupportedExternalAccessoryProtocols`. It requires `NSBluetoothPeripheralUsageDescription` additionally only for deployment targets earlier than iOS 13.
132
+
133
+ More information: https://support.brother.co.jp/j/s/support/html/mobilesdk/guide/getting-started/getting-started-ios.html