react-native-bluetooth-escpos-printer-fork 0.0.17 → 0.0.18

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 CHANGED
@@ -28,6 +28,21 @@ Link the plugin to your RN project
28
28
  react-native link react-native-bluetooth-escpos-printer
29
29
  ```
30
30
 
31
+ ### Step3 (iOS only) ###
32
+ Add Bluetooth permissions to your `Info.plist`:
33
+
34
+ ```xml
35
+ <key>NSBluetoothAlwaysUsageDescription</key>
36
+ <string>This app needs Bluetooth to discover and connect to printers</string>
37
+ <key>NSBluetoothPeripheralUsageDescription</key>
38
+ <string>This app needs Bluetooth to connect to printer peripherals</string>
39
+ ```
40
+
41
+ **Note:**
42
+ - For iOS 13+, `NSBluetoothAlwaysUsageDescription` is required.
43
+ - For iOS 12 and below, `NSBluetoothPeripheralUsageDescription` is required.
44
+ - It's recommended to include both for maximum compatibility.
45
+
31
46
  ### Manual linking (Android) ###
32
47
  Ensure your build files match the following requirements:
33
48
 
@@ -73,8 +88,12 @@ async function, checks whether Bluetooth service is enabled.
73
88
  });
74
89
  ```
75
90
 
76
- * enableBluetooth ==> ``` diff + ANDROID ONLY ```
77
- async function, enables the bluetooth service, returns the devices information already bound and paired. ``` diff - IOS would just resovle with nil ```
91
+ * enableBluetooth ==>
92
+ async function, enables the bluetooth service, returns the devices information already bound and paired.
93
+
94
+ **Platform differences:**
95
+ - **Android:** If Bluetooth is off, shows system dialog to enable it. Returns array of paired devices after enabling (or immediately if already enabled).
96
+ - **iOS:** Cannot programmatically enable Bluetooth (must be done in Settings by user). Returns empty array `[]` since iOS Core Bluetooth doesn't have a "paired devices" concept. Use `scanDevices()` to discover available devices.
78
97
 
79
98
  ```javascript
80
99
  BluetoothManager.enableBluetooth().then((r)=>{
@@ -94,8 +113,12 @@ BluetoothManager.enableBluetooth().then((r)=>{
94
113
  });
95
114
  ```
96
115
 
97
- * disableBluetooth ==> ``` diff + ANDROID ONLY ```
98
- async function ,disables the bluetooth service. ``` diff - IOS would just resovle with nil ```
116
+ * disableBluetooth ==>
117
+ async function, disables the bluetooth service.
118
+
119
+ **Platform differences:**
120
+ - **Android:** Programmatically disables Bluetooth and stops any active connections.
121
+ - **iOS:** Cannot programmatically disable Bluetooth (must be done in Settings or Control Center by user). This method resolves successfully but does nothing on iOS.
99
122
 
100
123
  ```javascript
101
124
  BluetoothManager.disableBluetooth().then(()=>{
@@ -139,6 +162,36 @@ BluetoothManager.scanDevices()
139
162
  });
140
163
  ```
141
164
 
165
+ **Device Information Structure:**
166
+
167
+ Each device object returned contains:
168
+ - `name` (string): Device name
169
+ - `address` (string): Device MAC address (Android) or UUID (iOS)
170
+ - `deviceClass` (number): Bluetooth device class
171
+ - `majorDeviceClass` (number): Major device class category
172
+ - `rssi` (number, iOS only): Signal strength indicator
173
+
174
+ **Filtering for Printers:**
175
+
176
+ To identify printer devices, check the `majorDeviceClass`:
177
+ ```javascript
178
+ const MAJOR_CLASS_IMAGING = 1536; // 0x0600 - Printers, scanners, cameras
179
+ const DEVICE_CLASS_PRINTER = 1664; // 0x0680 - Specifically printers
180
+
181
+ // Filter found devices to show only printers
182
+ const printers = ss.found.filter(device =>
183
+ device.majorDeviceClass === MAJOR_CLASS_IMAGING ||
184
+ device.deviceClass === DEVICE_CLASS_PRINTER
185
+ );
186
+ ```
187
+
188
+ **Platform differences:**
189
+ - **Android:** Device classes are provided by the OS based on the device's Bluetooth profile
190
+ - **iOS:** Device classes are inferred from:
191
+ 1. Advertised service UUIDs (SPP, ESC/POS services)
192
+ 2. Device name patterns (contains "print", "pos", "thermal", "receipt", etc.)
193
+ 3. If no printer indicators are found, device is marked as "unknown" (class 7936)
194
+
142
195
  * connect ==>
143
196
  async function, connects the specified device, if not bound, bound dailog prompts.
144
197
 
@@ -162,8 +215,10 @@ async function, connects the specified device, if not bound, bound dailog prompt
162
215
  * unpair ==>
163
216
  async function, disconnects and unpairs the specified devices
164
217
 
218
+ **Note for iOS:** Due to iOS security restrictions, this method can only disconnect the device but cannot programmatically unpair it. The device will remain paired at the iOS system level. Users must unpair manually from iOS Settings > Bluetooth if needed. On Android, full unpair functionality is supported.
219
+
165
220
  ```javascript
166
- BluetoothManager.connect(rowData.address)
221
+ BluetoothManager.unpaire(rowData.address)
167
222
  .then((s)=>{
168
223
  //success here
169
224
  },
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <classpath>
3
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
4
+ <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
5
+ <classpathentry kind="output" path="bin/default"/>
6
+ </classpath>
File without changes
@@ -0,0 +1,2 @@
1
+ #Sat Jan 31 21:27:45 CET 2026
2
+ gradle.version=6.1.1
File without changes
package/android/.project CHANGED
@@ -5,6 +5,11 @@
5
5
  <projects>
6
6
  </projects>
7
7
  <buildSpec>
8
+ <buildCommand>
9
+ <name>org.eclipse.jdt.core.javabuilder</name>
10
+ <arguments>
11
+ </arguments>
12
+ </buildCommand>
8
13
  <buildCommand>
9
14
  <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
10
15
  <arguments>
@@ -12,6 +17,18 @@
12
17
  </buildCommand>
13
18
  </buildSpec>
14
19
  <natures>
20
+ <nature>org.eclipse.jdt.core.javanature</nature>
15
21
  <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
16
22
  </natures>
23
+ <filteredResources>
24
+ <filter>
25
+ <id>1769891269456</id>
26
+ <name></name>
27
+ <type>30</type>
28
+ <matcher>
29
+ <id>org.eclipse.core.resources.regexFilterMatcher</id>
30
+ <arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
31
+ </matcher>
32
+ </filter>
33
+ </filteredResources>
17
34
  </projectDescription>
@@ -1,2 +1,13 @@
1
+ arguments=--init-script /Users/lukaszprivate/.local/share/opencode/bin/jdtls/config_mac/org.eclipse.osgi/59/0/.cp/gradle/init/init.gradle
2
+ auto.sync=false
3
+ build.scans.enabled=false
4
+ connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
1
5
  connection.project.dir=
2
6
  eclipse.preferences.version=1
7
+ gradle.user.home=
8
+ java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
9
+ jvm.arguments=
10
+ offline.mode=false
11
+ override.workspace.settings=true
12
+ show.console.view=true
13
+ show.executions.view=true
@@ -0,0 +1,4 @@
1
+ eclipse.preferences.version=1
2
+ org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
3
+ org.eclipse.jdt.core.compiler.compliance=11
4
+ org.eclipse.jdt.core.compiler.source=11
@@ -12,7 +12,7 @@
12
12
 
13
13
 
14
14
  - (void) didWriteDataToBle: (BOOL)success
15
- {NSLog(@"PrintImageBleWriteDelete diWriteDataToBle: %d",success?1:0);
15
+ {
16
16
  if(success){
17
17
  if(_now == -1){
18
18
  if(_pendingResolve) {_pendingResolve(nil); _pendingResolve=nil;}
@@ -43,19 +43,10 @@
43
43
  {
44
44
  @synchronized (self) {
45
45
  NSInteger sizePerLine = (int)(_width/8);
46
- // do{
47
- // if(sizePerLine+_now>=[_toPrint length]){
48
- // sizePerLine = [_toPrint length] - _now;
49
- // }
50
- // if(sizePerLine>0){
51
- NSData *subData = [_toPrint subdataWithRange:NSMakeRange(_now, sizePerLine)];
52
- NSLog(@"Write data:%@",subData);
53
- [RNBluetoothManager writeValue:subData withDelegate:self];
54
- //}
46
+ NSData *subData = [_toPrint subdataWithRange:NSMakeRange(_now, sizePerLine)];
47
+ [RNBluetoothManager writeValue:subData withDelegate:self];
55
48
  _now = _now+sizePerLine;
56
49
  [NSThread sleepForTimeInterval:0.01f];
57
-
58
50
  }
59
- //}while(_now<[_toPrint length]);
60
51
  }
61
52
  @end
@@ -560,6 +560,7 @@ RCT_EXPORT_METHOD(printEscPosCommands:(NSString *)base64encodeStr
560
560
  if(RNBluetoothManager.isConnected){
561
561
  @try{
562
562
  NSData *decoded = [[NSData alloc] initWithBase64EncodedString:base64encodeStr options:0];
563
+
563
564
  pendingResolve = resolve;
564
565
  pendingReject = reject;
565
566
  [RNBluetoothManager writeValue:decoded withDelegate:self];
@@ -19,6 +19,7 @@
19
19
  @property (nonatomic,copy) RCTPromiseResolveBlock scanResolveBlock;
20
20
  @property (nonatomic,copy) RCTPromiseRejectBlock scanRejectBlock;
21
21
  @property (strong,nonatomic) NSMutableDictionary <NSString *,CBPeripheral *> *foundDevices;
22
+ @property (strong,nonatomic) NSMutableDictionary <NSString *,NSDictionary *> *foundDevicesMetadata;
22
23
  @property (strong,nonatomic) NSString *waitingConnect;
23
24
  @property (nonatomic,copy) RCTPromiseResolveBlock connectResolveBlock;
24
25
  @property (nonatomic,copy) RCTPromiseRejectBlock connectRejectBlock;
@@ -21,23 +21,104 @@ static NSArray<CBUUID *> *supportServices = nil;
21
21
  static NSDictionary *writeableCharactiscs = nil;
22
22
  bool hasListeners;
23
23
  static CBPeripheral *connected;
24
+ static CBCharacteristic *cachedWriteCharacteristic = nil; // Cache the writable characteristic
24
25
  static RNBluetoothManager *instance;
25
26
  static NSObject<WriteDataToBleDelegate> *writeDataDelegate;// delegate of write data resule;
26
27
  static NSData *toWrite;
27
28
  static NSTimer *timer;
28
29
 
30
+ -(instancetype)init {
31
+ if (self = [super init]) {
32
+ // Initialize CBCentralManager eagerly so it's ready when methods are called
33
+ // This triggers the centralManagerDidUpdateState callback immediately
34
+ [self centralManager];
35
+ [self initSupportServices];
36
+ }
37
+ return self;
38
+ }
39
+
29
40
  +(Boolean)isConnected{
30
41
  return !(connected==nil);
31
42
  }
32
43
 
44
+ // Helper method to write data with automatic chunking for BLE MTU limits
45
+ +(BOOL)writeDataWithChunking:(NSData *)data
46
+ toCharacteristic:(CBCharacteristic *)characteristic
47
+ onPeripheral:(CBPeripheral *)peripheral {
48
+ CBCharacteristicWriteType writeType = (characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse)
49
+ ? CBCharacteristicWriteWithoutResponse
50
+ : CBCharacteristicWriteWithResponse;
51
+
52
+ // BLE has MTU limitations - need to chunk data if it's too large
53
+ // The minimum BLE MTU is 23 bytes, with 3 bytes overhead, leaving 20 bytes usable
54
+ // maximumWriteValueLengthForType often returns incorrect values for WriteWithoutResponse
55
+ // so we force a conservative chunk size
56
+ NSUInteger maxWriteLength = 20; // Safe default that works with all BLE devices
57
+
58
+ NSUInteger dataLength = [data length];
59
+
60
+ if(dataLength <= maxWriteLength) {
61
+ // Data fits in one write - no chunking needed
62
+ [peripheral writeValue:data forCharacteristic:characteristic type:writeType];
63
+ return YES;
64
+ } else {
65
+ // Need to chunk the data
66
+ NSLog(@"BLE chunking: %lu bytes -> %lu byte chunks", dataLength, maxWriteLength);
67
+ NSUInteger offset = 0;
68
+ BOOL allSuccess = YES;
69
+ NSUInteger chunkCount = 0;
70
+
71
+ while(offset < dataLength && allSuccess) {
72
+ NSUInteger chunkSize = MIN(maxWriteLength, dataLength - offset);
73
+ NSData *chunk = [data subdataWithRange:NSMakeRange(offset, chunkSize)];
74
+
75
+ @try {
76
+ [peripheral writeValue:chunk forCharacteristic:characteristic type:writeType];
77
+ chunkCount++;
78
+
79
+ // Small delay between chunks to avoid overwhelming the device
80
+ if(writeType == CBCharacteristicWriteWithoutResponse) {
81
+ [NSThread sleepForTimeInterval:0.01]; // 10ms delay
82
+ }
83
+ }
84
+ @catch(NSException *e) {
85
+ NSLog(@"BLE chunk write error at offset %lu: %@", offset, e);
86
+ allSuccess = NO;
87
+ }
88
+
89
+ offset += chunkSize;
90
+ }
91
+
92
+ if(allSuccess) {
93
+ NSLog(@"BLE chunking complete: %lu chunks sent", chunkCount);
94
+ }
95
+ return allSuccess;
96
+ }
97
+ }
98
+
33
99
  +(void)writeValue:(NSData *) data withDelegate:(NSObject<WriteDataToBleDelegate> *) delegate
34
100
  {
35
101
  @try{
36
102
  writeDataDelegate = delegate;
37
103
  toWrite = data;
38
104
  connected.delegate = instance;
39
- [connected discoverServices:supportServices];
40
- // [connected writeValue:data forCharacteristic:[writeableCharactiscs objectForKey:supportServices[0]] type:CBCharacteristicWriteWithoutResponse];
105
+
106
+ // If we have a cached writable characteristic, use it directly
107
+ if(cachedWriteCharacteristic && connected){
108
+ // Use helper method to write with chunking
109
+ BOOL success = [RNBluetoothManager writeDataWithChunking:toWrite
110
+ toCharacteristic:cachedWriteCharacteristic
111
+ onPeripheral:connected];
112
+
113
+ if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:success];
114
+ toWrite = nil;
115
+ return;
116
+ }
117
+
118
+ // No cached characteristic, need to discover services first
119
+ NSLog(@"No cached characteristic, discovering services...");
120
+ // Discover all services (not just SPP which is for Classic BT, not BLE)
121
+ [connected discoverServices:nil];
41
122
  }
42
123
  @catch(NSException *e){
43
124
  NSLog(@"error in writing data to %@,issue:%@",connected,e);
@@ -119,7 +200,11 @@ RCT_EXPORT_METHOD(isBluetoothEnabled:(RCTPromiseResolveBlock)resolve
119
200
  RCT_EXPORT_METHOD(enableBluetooth:(RCTPromiseResolveBlock)resolve
120
201
  rejecter:(RCTPromiseRejectBlock)reject)
121
202
  {
122
- resolve(nil);
203
+ // iOS doesn't allow programmatic Bluetooth enabling
204
+ // Bluetooth must be enabled from Settings by the user
205
+ // Return empty array to match Android's return type (array of paired devices)
206
+ // Since iOS Core Bluetooth doesn't have "paired devices" concept, we return empty array
207
+ resolve(@[]);
123
208
  }
124
209
  //disableBluetooth
125
210
  RCT_EXPORT_METHOD(disableBluetooth:(RCTPromiseResolveBlock)resolve
@@ -141,15 +226,50 @@ RCT_EXPORT_METHOD(scanDevices:(RCTPromiseResolveBlock)resolve
141
226
  }
142
227
  self.scanResolveBlock = resolve;
143
228
  self.scanRejectBlock = reject;
229
+
230
+ // Initialize metadata dictionary if needed
231
+ if(!self.foundDevicesMetadata){
232
+ self.foundDevicesMetadata = [[NSMutableDictionary alloc] init];
233
+ }
234
+
144
235
  if(connected && connected.identifier){
145
- NSDictionary *idAndName =@{@"address":connected.identifier.UUIDString,@"name":connected.name?connected.name:@""};
146
- NSDictionary *peripheralStored = @{connected.identifier.UUIDString:connected};
236
+ NSString *name = connected.name ? connected.name : @"";
237
+ NSString *address = connected.identifier.UUIDString;
238
+
239
+ // Create device info with device class for connected device
240
+ NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc] init];
241
+ [deviceInfo setObject:address forKey:@"address"];
242
+ [deviceInfo setObject:name forKey:@"name"];
243
+ [deviceInfo setObject:@(0) forKey:@"rssi"]; // Unknown RSSI for already connected device
244
+
245
+ // Try to infer if it's a printer from the name
246
+ BOOL isPrinter = NO;
247
+ NSString *upperName = name.uppercaseString;
248
+ if ([upperName containsString:@"PRINT"] ||
249
+ [upperName containsString:@"POS"] ||
250
+ [upperName containsString:@"ESCPOS"] ||
251
+ [upperName containsString:@"THERMAL"] ||
252
+ [upperName containsString:@"RECEIPT"]) {
253
+ isPrinter = YES;
254
+ }
255
+
256
+ if (isPrinter) {
257
+ [deviceInfo setObject:@(1536) forKey:@"majorDeviceClass"]; // Imaging
258
+ [deviceInfo setObject:@(1664) forKey:@"deviceClass"]; // Printer
259
+ } else {
260
+ [deviceInfo setObject:@(7936) forKey:@"majorDeviceClass"]; // Unknown
261
+ [deviceInfo setObject:@(7936) forKey:@"deviceClass"]; // Unknown
262
+ }
263
+
264
+ NSDictionary *peripheralStored = @{address:connected};
147
265
  if(!self.foundDevices){
148
266
  self.foundDevices = [[NSMutableDictionary alloc] init];
149
267
  }
150
268
  [self.foundDevices addEntriesFromDictionary:peripheralStored];
269
+ [self.foundDevicesMetadata setObject:deviceInfo forKey:address];
270
+
151
271
  if(hasListeners){
152
- [self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":idAndName}];
272
+ [self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":deviceInfo}];
153
273
  }
154
274
  }
155
275
  [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
@@ -183,6 +303,13 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
183
303
  rejecter:(RCTPromiseRejectBlock)reject)
184
304
  {
185
305
  NSLog(@"Trying to connect....%@",address);
306
+
307
+ // Check if Bluetooth is ready
308
+ if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
309
+ reject(@"BLUETOOTH_NOT_READY",@"Bluetooth is not enabled or not ready. Please enable Bluetooth and try again.",nil);
310
+ return;
311
+ }
312
+
186
313
  [self callStop];
187
314
  if(connected){
188
315
  NSString *connectedAddress =connected.identifier.UUIDString;
@@ -205,6 +332,13 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
205
332
  // Callbacks:
206
333
  // centralManager:didConnectPeripheral:
207
334
  // centralManager:didFailToConnectPeripheral:error:
335
+
336
+ // Set connection timeout (30 seconds)
337
+ if(timer && timer.isValid){
338
+ [timer invalidate];
339
+ timer = nil;
340
+ }
341
+ timer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(handleConnectTimeout) userInfo:nil repeats:NO];
208
342
  }else{
209
343
  //starts the scan.
210
344
  _waitingConnect = address;
@@ -212,32 +346,125 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
212
346
  [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
213
347
  //Callbacks:
214
348
  //centralManager:didDiscoverPeripheral:advertisementData:RSSI:
349
+
350
+ // Set scan + connection timeout (30 seconds)
351
+ if(timer && timer.isValid){
352
+ [timer invalidate];
353
+ timer = nil;
354
+ }
355
+ timer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(handleConnectTimeout) userInfo:nil repeats:NO];
356
+ }
357
+ }
358
+ //disconnect(address)
359
+ RCT_EXPORT_METHOD(disconnect:(NSString *)address
360
+ findEventsWithResolver:(RCTPromiseResolveBlock)resolve
361
+ rejecter:(RCTPromiseRejectBlock)reject)
362
+ {
363
+ NSLog(@"Trying to disconnect device: %@",address);
364
+
365
+ // Check if Bluetooth is ready
366
+ if(!self.centralManager || self.centralManager.state!=CBManagerStatePoweredOn){
367
+ reject(@"BLUETOOTH_NOT_READY",@"Bluetooth is not enabled or not ready.",nil);
368
+ return;
369
+ }
370
+
371
+ if(connected && [connected.identifier.UUIDString isEqualToString:address]){
372
+ [self.centralManager cancelPeripheralConnection:connected];
373
+ // Note: disconnection is async, but we resolve immediately like Android does
374
+ // The didDisconnectPeripheral callback will handle setting connected = nil
375
+ resolve(address);
376
+ } else {
377
+ // Device not connected or different device
378
+ NSLog(@"Device not connected or different device: %@",address);
379
+ resolve(address);
215
380
  }
216
381
  }
217
382
  //unpaire(address)
383
+ RCT_EXPORT_METHOD(unpaire:(NSString *)address
384
+ findEventsWithResolver:(RCTPromiseResolveBlock)resolve
385
+ rejecter:(RCTPromiseRejectBlock)reject)
386
+ {
387
+ NSLog(@"Trying to unpair/disconnect device: %@",address);
388
+ if(connected && [connected.identifier.UUIDString isEqualToString:address]){
389
+ [self.centralManager cancelPeripheralConnection:connected];
390
+ // Note: Core Bluetooth on iOS doesn't support programmatic unpairing
391
+ // The connection will be cancelled, but the device remains paired at OS level
392
+ // Users must unpair manually from iOS Settings if needed
393
+ connected = nil;
394
+ resolve(address);
395
+ } else {
396
+ // Device not connected, just resolve
397
+ NSLog(@"Device not connected, nothing to unpair: %@",address);
398
+ resolve(address);
399
+ }
400
+ }
401
+
218
402
 
403
+ -(void)handleConnectTimeout{
404
+ NSLog(@"Connection timeout - device not found or unable to connect");
405
+ if(self.centralManager.isScanning){
406
+ [self.centralManager stopScan];
407
+ }
408
+ if(self.connectRejectBlock){
409
+ RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
410
+ rjBlock(@"CONNECTION_TIMEOUT",@"Could not connect to device - timeout after 30 seconds",nil);
411
+ self.connectRejectBlock = nil;
412
+ self.connectResolveBlock = nil;
413
+ }
414
+ _waitingConnect = nil;
415
+ if(timer && timer.isValid){
416
+ [timer invalidate];
417
+ timer = nil;
418
+ }
419
+ }
219
420
 
220
421
  -(void)callStop{
221
422
  if(self.centralManager.isScanning){
222
423
  [self.centralManager stopScan];
223
424
  NSMutableArray *devices = [[NSMutableArray alloc] init];
224
425
  for(NSString *key in self.foundDevices){
225
- NSLog(@"insert found devies:%@ =>%@",key,[self.foundDevices objectForKey:key]);
226
- NSString *name = [self.foundDevices objectForKey:key].name;
227
- if(!name){
228
- name = @"";
426
+ NSLog(@"insert found devices:%@ =>%@",key,[self.foundDevices objectForKey:key]);
427
+
428
+ // Get stored metadata if available, otherwise create basic info
429
+ NSDictionary *deviceInfo = [self.foundDevicesMetadata objectForKey:key];
430
+ if (deviceInfo) {
431
+ [devices addObject:deviceInfo];
432
+ } else {
433
+ // Fallback if metadata wasn't stored (shouldn't happen with new code)
434
+ NSString *name = [self.foundDevices objectForKey:key].name;
435
+ if(!name){
436
+ name = @"";
437
+ }
438
+ [devices addObject:@{
439
+ @"address":key,
440
+ @"name":name,
441
+ @"deviceClass":@(7936), // Unknown device class
442
+ @"majorDeviceClass":@(7936) // Unknown major device class
443
+ }];
229
444
  }
230
- [devices addObject:@{@"address":key,@"name":name}];
231
445
  }
446
+
447
+ // Create result object matching Android format: {"paired": [...], "found": [...]}
448
+ // Empty paired devices array (iOS doesn't have "paired" concept like Android)
449
+ NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
450
+ [result setObject:@[] forKey:@"paired"];
451
+ [result setObject:devices forKey:@"found"];
452
+
453
+ // Convert the entire result to a JSON string (matching Android's behavior)
232
454
  NSError *error = nil;
233
- NSData* jsonData = [NSJSONSerialization dataWithJSONObject:devices options:NSJSONWritingPrettyPrinted error:&error];
234
- NSString * jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
455
+ NSData* resultJsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:&error];
456
+ NSString *resultJsonStr = [[NSString alloc] initWithData:resultJsonData encoding:NSUTF8StringEncoding];
457
+
235
458
  if(hasListeners){
236
- [self sendEventWithName:EVENT_DEVICE_DISCOVER_DONE body:@{@"found":jsonStr,@"paired":@"[]"}];
459
+ // For events, send separate paired and found strings
460
+ NSData* foundJsonData = [NSJSONSerialization dataWithJSONObject:devices options:0 error:&error];
461
+ NSString *foundJsonStr = [[NSString alloc] initWithData:foundJsonData encoding:NSUTF8StringEncoding];
462
+ [self sendEventWithName:EVENT_DEVICE_DISCOVER_DONE body:@{@"found":foundJsonStr,@"paired":@"[]"}];
237
463
  }
238
464
  if(self.scanResolveBlock){
239
465
  RCTPromiseResolveBlock rsBlock = self.scanResolveBlock;
240
- rsBlock(@{@"found":jsonStr,@"paired":@"[]"});
466
+ // Return single JSON string: "{\"paired\":[],\"found\":[...]}"
467
+ rsBlock(resultJsonStr);
241
468
  self.scanResolveBlock = nil;
242
469
  }
243
470
  }
@@ -251,9 +478,11 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
251
478
  - (void) initSupportServices
252
479
  {
253
480
  if(!supportServices){
254
- CBUUID *issc = [CBUUID UUIDWithString: @"49535343-FE7D-4AE5-8FA9-9FAFD205E455"];
255
- supportServices = [NSArray arrayWithObject:issc];/*ISSC*/
256
- writeableCharactiscs = @{issc:@"49535343-8841-43F4-A8D4-ECBE34729BB3"};
481
+ // Use standard SPP (Serial Port Profile) UUID - same as Android
482
+ CBUUID *spp = [CBUUID UUIDWithString: @"00001101-0000-1000-8000-00805F9B34FB"];
483
+ supportServices = [NSArray arrayWithObject:spp];
484
+ // SPP uses the same UUID for service and characteristic
485
+ writeableCharactiscs = @{spp:@"00001101-0000-1000-8000-00805F9B34FB"};
257
486
  }
258
487
  }
259
488
 
@@ -284,19 +513,108 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
284
513
  * CBCentralManagerDelegate
285
514
  **/
286
515
  - (void)centralManagerDidUpdateState:(CBCentralManager *)central{
287
- NSLog(@"%ld",(long)central.state);
516
+ NSString *stateString;
517
+ switch (central.state) {
518
+ case CBManagerStateUnknown:
519
+ stateString = @"Unknown";
520
+ break;
521
+ case CBManagerStateResetting:
522
+ stateString = @"Resetting";
523
+ break;
524
+ case CBManagerStateUnsupported:
525
+ stateString = @"Unsupported";
526
+ break;
527
+ case CBManagerStateUnauthorized:
528
+ stateString = @"Unauthorized";
529
+ break;
530
+ case CBManagerStatePoweredOff:
531
+ stateString = @"PoweredOff";
532
+ break;
533
+ case CBManagerStatePoweredOn:
534
+ stateString = @"PoweredOn";
535
+ break;
536
+ default:
537
+ stateString = @"Unknown";
538
+ break;
539
+ }
540
+ NSLog(@"Bluetooth State Changed: %@ (%ld)", stateString, (long)central.state);
288
541
  }
289
542
 
290
543
  - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
291
544
  NSLog(@"did discover peripheral: %@",peripheral);
292
- NSDictionary *idAndName =@{@"address":peripheral.identifier.UUIDString,@"name":peripheral.name?peripheral.name:@""};
545
+
546
+ // Extract device information with enhanced metadata
547
+ NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc] init];
548
+ [deviceInfo setObject:peripheral.identifier.UUIDString forKey:@"address"];
549
+ [deviceInfo setObject:(peripheral.name ? peripheral.name : @"") forKey:@"name"];
550
+
551
+ // Add RSSI for signal strength
552
+ [deviceInfo setObject:RSSI forKey:@"rssi"];
553
+
554
+ // Try to infer device class from advertising data
555
+ // iOS BLE doesn't expose Classic Bluetooth device class, but we can make educated guesses
556
+ NSInteger deviceClass = 0;
557
+ NSInteger majorDeviceClass = 0;
558
+
559
+ // Check if device advertises printer-related services
560
+ NSArray *serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey];
561
+ BOOL isPrinter = NO;
562
+
563
+ if (serviceUUIDs && serviceUUIDs.count > 0) {
564
+ for (CBUUID *uuid in serviceUUIDs) {
565
+ NSString *uuidString = uuid.UUIDString.uppercaseString;
566
+ // Serial Port Profile (SPP) UUID - common for printers
567
+ // Also check for other printer-related service UUIDs
568
+ if ([uuidString isEqualToString:@"00001101-0000-1000-8000-00805F9B34FB"] ||
569
+ [uuidString isEqualToString:@"49535343-FE7D-4AE5-8FA9-9FAFD205E455"] ||
570
+ [uuidString containsString:@"18F0"]) {
571
+ isPrinter = YES;
572
+ break;
573
+ }
574
+ }
575
+ }
576
+
577
+ // Check device name for printer keywords
578
+ NSString *name = peripheral.name ? peripheral.name.uppercaseString : @"";
579
+ if ([name containsString:@"PRINT"] ||
580
+ [name containsString:@"POS"] ||
581
+ [name containsString:@"ESCPOS"] ||
582
+ [name containsString:@"THERMAL"] ||
583
+ [name containsString:@"RECEIPT"]) {
584
+ isPrinter = YES;
585
+ }
586
+
587
+ if (isPrinter) {
588
+ // Android Bluetooth Class values for printers:
589
+ // Major Device Class: 0x0600 (Imaging - printers, scanners, cameras, etc.)
590
+ // Device Class: 0x000680 (Imaging/Printer)
591
+ majorDeviceClass = 1536; // 0x0600 in decimal
592
+ deviceClass = 1664; // 0x0680 in decimal
593
+ } else {
594
+ // Unknown/Unclassified device
595
+ // Major Device Class: 0x1F00 (Uncategorized)
596
+ majorDeviceClass = 7936; // 0x1F00 in decimal
597
+ deviceClass = 7936;
598
+ }
599
+
600
+ [deviceInfo setObject:@(deviceClass) forKey:@"deviceClass"];
601
+ [deviceInfo setObject:@(majorDeviceClass) forKey:@"majorDeviceClass"];
602
+
603
+ // Store the peripheral object for connection
293
604
  NSDictionary *peripheralStored = @{peripheral.identifier.UUIDString:peripheral};
294
605
  if(!self.foundDevices){
295
606
  self.foundDevices = [[NSMutableDictionary alloc] init];
296
607
  }
297
608
  [self.foundDevices addEntriesFromDictionary:peripheralStored];
609
+
610
+ // Store device metadata for later retrieval
611
+ if(!self.foundDevicesMetadata){
612
+ self.foundDevicesMetadata = [[NSMutableDictionary alloc] init];
613
+ }
614
+ [self.foundDevicesMetadata setObject:deviceInfo forKey:peripheral.identifier.UUIDString];
615
+
298
616
  if(hasListeners){
299
- [self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":idAndName}];
617
+ [self sendEventWithName:EVENT_DEVICE_FOUND body:@{@"device":deviceInfo}];
300
618
  }
301
619
  if(_waitingConnect && [_waitingConnect isEqualToString: peripheral.identifier.UUIDString]){
302
620
  [self.centralManager connectPeripheral:peripheral options:nil];
@@ -308,10 +626,24 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
308
626
  NSLog(@"did connected: %@",peripheral);
309
627
  connected = peripheral;
310
628
  NSString *pId = peripheral.identifier.UUIDString;
629
+
630
+ // Set peripheral delegate to receive service/characteristic callbacks
631
+ peripheral.delegate = self;
632
+
311
633
  if(_waitingConnect && [_waitingConnect isEqualToString: pId] && self.connectResolveBlock){
312
- NSLog(@"Predefined the support services, stop to looking up services.");
313
- // peripheral.delegate=self;
314
- // [peripheral discoverServices:nil];
634
+ // Cancel connection timeout timer if it's running
635
+ if(timer && timer.isValid){
636
+ [timer invalidate];
637
+ timer = nil;
638
+ }
639
+
640
+ // Pre-discover services to prepare for future write operations
641
+ // This matches Android's behavior where the connection is fully ready after connect
642
+ // Pass nil to discover ALL services (not just SPP which is for Classic BT, not BLE)
643
+ NSLog(@"Pre-discovering all services for faster write operations...");
644
+ [peripheral discoverServices:nil];
645
+
646
+ // Resolve promise immediately (iOS BLE connects fast, service discovery happens async)
315
647
  self.connectResolveBlock(nil);
316
648
  _waitingConnect = nil;
317
649
  self.connectRejectBlock = nil;
@@ -324,6 +656,9 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
324
656
  }
325
657
 
326
658
  - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
659
+ // Clear cached characteristic when disconnecting
660
+ cachedWriteCharacteristic = nil;
661
+
327
662
  if(!connected && _waitingConnect && [_waitingConnect isEqualToString:peripheral.identifier.UUIDString]){
328
663
  if(self.connectRejectBlock){
329
664
  RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
@@ -345,6 +680,9 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
345
680
  }
346
681
 
347
682
  - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
683
+ // Clear cached characteristic when connection fails
684
+ cachedWriteCharacteristic = nil;
685
+
348
686
  if(self.connectRejectBlock){
349
687
  RCTPromiseRejectBlock rjBlock = self.connectRejectBlock;
350
688
  rjBlock(@"",@"",error);
@@ -412,38 +750,59 @@ RCT_EXPORT_METHOD(connect:(NSString *)address
412
750
  */
413
751
  - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error{
414
752
  if(toWrite && connected
415
- && [connected.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]
416
- && [service.UUID.UUIDString isEqualToString:supportServices[0].UUIDString]){
753
+ && [connected.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]){
417
754
  if(error){
418
- NSLog(@"Discrover charactoreristics error:%@",error);
755
+ NSLog(@"Discover characteristics error:%@",error);
419
756
  if(writeDataDelegate)
420
757
  {
421
758
  [writeDataDelegate didWriteDataToBle:false];
422
759
  return;
423
760
  }
424
761
  }
762
+
763
+ // Try to write to any service that has writable characteristics
764
+ // Don't filter by supportServices - many BLE printers use custom UUIDs
765
+ NSLog(@"Checking service %@ for writable characteristics...", service.UUID.UUIDString);
766
+
767
+ // Auto-detect writable characteristic by checking properties
425
768
  for(CBCharacteristic *cc in service.characteristics){
426
- NSLog(@"Characterstic found: %@ in service: %@" ,cc,service.UUID.UUIDString);
427
- if([cc.UUID.UUIDString isEqualToString:[writeableCharactiscs objectForKey: supportServices[0]]]){
769
+ NSLog(@"Characteristic found: %@ in service: %@" ,cc,service.UUID.UUIDString);
770
+
771
+ // Check if characteristic supports write operations
772
+ BOOL canWrite = (cc.properties & CBCharacteristicPropertyWrite) ||
773
+ (cc.properties & CBCharacteristicPropertyWriteWithoutResponse);
774
+
775
+ if(canWrite){
428
776
  @try{
429
- [connected writeValue:toWrite forCharacteristic:cc type:CBCharacteristicWriteWithoutResponse];
430
- if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:true];
431
- if(toWrite){
432
- NSLog(@"Value wrote: %lu",[toWrite length]);
433
- }
777
+ NSLog(@"Writing %lu bytes to characteristic %@ in service %@",[toWrite length], cc.UUID.UUIDString, service.UUID.UUIDString);
778
+
779
+ // Use helper method to write with chunking
780
+ BOOL success = [RNBluetoothManager writeDataWithChunking:toWrite
781
+ toCharacteristic:cc
782
+ onPeripheral:connected];
783
+
784
+ // Cache this characteristic for future writes
785
+ cachedWriteCharacteristic = cc;
786
+ NSLog(@"Cached characteristic %@ for future writes", cc.UUID.UUIDString);
787
+
788
+ if(writeDataDelegate) [writeDataDelegate didWriteDataToBle:success];
789
+ toWrite = nil; // Clear the write buffer
790
+ return; // Success, exit after first successful write
434
791
  }
435
792
  @catch(NSException *e){
436
- NSLog(@"ERRO IN WRITE VALUE: %@",e);
437
- [writeDataDelegate didWriteDataToBle:false];
793
+ NSLog(@"ERROR IN WRITE VALUE: %@",e);
794
+ [writeDataDelegate didWriteDataToBle:false];
438
795
  }
439
796
  }
440
797
  }
441
798
 
442
-
799
+ // If we get here, no writable characteristic was found in this service
800
+ NSLog(@"No writable characteristic found in service: %@", service.UUID.UUIDString);
801
+ // Don't call didWriteDataToBle:false here - there might be other services to check
443
802
  }
444
803
 
445
804
  if(error){
446
- NSLog(@"Discrover charactoreristics error:%@",error);
805
+ NSLog(@"Discover characteristics error:%@",error);
447
806
  return;
448
807
  }
449
808
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-bluetooth-escpos-printer-fork",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "React-Native plugin for the bluetooth ESC/POS printers.",
5
5
  "main": "index.js",
6
6
  "scripts": {