munim-bluetooth 0.3.27 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +476 -74
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/cpp/cpp-adapter.cpp +4 -1
  6. package/android/src/main/java/com/munimbluetooth/HybridMunimBluetooth.kt +2006 -209
  7. package/android/src/main/java/com/munimbluetooth/MunimBluetoothBackgroundService.kt +561 -53
  8. package/app.plugin.js +155 -0
  9. package/ios/HybridMunimBluetooth.swift +2123 -298
  10. package/ios/MunimBluetoothEventEmitter.swift +68 -8
  11. package/lib/commonjs/index.js +272 -11
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/module/index.js +243 -11
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/typescript/src/index.d.ts +310 -7
  16. package/lib/typescript/src/index.d.ts.map +1 -1
  17. package/lib/typescript/src/specs/munim-bluetooth.nitro.d.ts +219 -5
  18. package/lib/typescript/src/specs/munim-bluetooth.nitro.d.ts.map +1 -1
  19. package/nitro.json +9 -3
  20. package/nitrogen/generated/android/c++/JAdvertisingDataTypes.hpp +96 -96
  21. package/nitrogen/generated/android/c++/JAdvertisingOptions.hpp +8 -8
  22. package/nitrogen/generated/android/c++/JBackgroundSessionOptions.hpp +8 -8
  23. package/nitrogen/generated/android/c++/JBluetoothCapabilities.hpp +105 -0
  24. package/nitrogen/generated/android/c++/JBluetoothPhy.hpp +61 -0
  25. package/nitrogen/generated/android/c++/JBluetoothPhyOption.hpp +61 -0
  26. package/nitrogen/generated/android/c++/JBondState.hpp +64 -0
  27. package/nitrogen/generated/android/c++/JDescriptorValue.hpp +69 -0
  28. package/nitrogen/generated/android/c++/JExtendedAdvertisingOptions.hpp +131 -0
  29. package/nitrogen/generated/android/c++/JGATTCharacteristic.hpp +35 -11
  30. package/nitrogen/generated/android/c++/JGATTDescriptor.hpp +85 -0
  31. package/nitrogen/generated/android/c++/JGATTService.hpp +33 -9
  32. package/nitrogen/generated/android/c++/JHybridMunimBluetoothSpec.cpp +422 -12
  33. package/nitrogen/generated/android/c++/JHybridMunimBluetoothSpec.hpp +29 -0
  34. package/nitrogen/generated/android/c++/JL2CAPChannel.hpp +66 -0
  35. package/nitrogen/generated/android/c++/JMultipeerDiscoveryInfoEntry.hpp +61 -0
  36. package/nitrogen/generated/android/c++/JMultipeerEncryptionPreference.hpp +61 -0
  37. package/nitrogen/generated/android/c++/JMultipeerPeer.hpp +93 -0
  38. package/nitrogen/generated/android/c++/JMultipeerPeerState.hpp +61 -0
  39. package/nitrogen/generated/android/c++/JMultipeerSessionOptions.hpp +105 -0
  40. package/nitrogen/generated/android/c++/JPhyStatus.hpp +62 -0
  41. package/nitrogen/generated/android/c++/JScanOptions.hpp +8 -8
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/AdvertisingDataTypes.kt +47 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/AdvertisingOptions.kt +19 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BackgroundSessionOptions.kt +27 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BluetoothCapabilities.kt +111 -0
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BluetoothPhy.kt +24 -0
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BluetoothPhyOption.kt +24 -0
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BondState.kt +25 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/CharacteristicValue.kt +17 -0
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/DescriptorValue.kt +66 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/ExtendedAdvertisingOptions.kt +111 -0
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/GATTCharacteristic.kt +25 -3
  53. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/GATTDescriptor.kt +61 -0
  54. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/GATTService.kt +23 -3
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/HybridMunimBluetoothSpec.kt +138 -22
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/L2CAPChannel.kt +61 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerDiscoveryInfoEntry.kt +56 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerEncryptionPreference.kt +24 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerPeer.kt +66 -0
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerPeerState.kt +24 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerSessionOptions.kt +81 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/PhyStatus.kt +56 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/ScanOptions.kt +17 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/ServiceDataEntry.kt +15 -0
  65. package/nitrogen/generated/ios/MunimBluetooth+autolinking.rb +2 -0
  66. package/nitrogen/generated/ios/MunimBluetooth-Swift-Cxx-Bridge.cpp +61 -5
  67. package/nitrogen/generated/ios/MunimBluetooth-Swift-Cxx-Bridge.hpp +494 -49
  68. package/nitrogen/generated/ios/MunimBluetooth-Swift-Cxx-Umbrella.hpp +42 -0
  69. package/nitrogen/generated/ios/c++/HybridMunimBluetoothSpecSwift.hpp +254 -0
  70. package/nitrogen/generated/ios/swift/BluetoothCapabilities.swift +89 -0
  71. package/nitrogen/generated/ios/swift/BluetoothPhy.swift +44 -0
  72. package/nitrogen/generated/ios/swift/BluetoothPhyOption.swift +44 -0
  73. package/nitrogen/generated/ios/swift/BondState.swift +48 -0
  74. package/nitrogen/generated/ios/swift/DescriptorValue.swift +44 -0
  75. package/nitrogen/generated/ios/swift/ExtendedAdvertisingOptions.swift +243 -0
  76. package/nitrogen/generated/ios/swift/Func_void_BluetoothCapabilities.swift +46 -0
  77. package/nitrogen/generated/ios/swift/Func_void_BondState.swift +46 -0
  78. package/nitrogen/generated/ios/swift/Func_void_DescriptorValue.swift +46 -0
  79. package/nitrogen/generated/ios/swift/Func_void_L2CAPChannel.swift +46 -0
  80. package/nitrogen/generated/ios/swift/Func_void_PhyStatus.swift +46 -0
  81. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  82. package/nitrogen/generated/ios/swift/Func_void_std__vector_MultipeerPeer_.swift +46 -0
  83. package/nitrogen/generated/ios/swift/GATTCharacteristic.swift +25 -1
  84. package/nitrogen/generated/ios/swift/GATTDescriptor.swift +71 -0
  85. package/nitrogen/generated/ios/swift/GATTService.swift +25 -1
  86. package/nitrogen/generated/ios/swift/HybridMunimBluetoothSpec.swift +29 -0
  87. package/nitrogen/generated/ios/swift/HybridMunimBluetoothSpec_cxx.swift +556 -23
  88. package/nitrogen/generated/ios/swift/L2CAPChannel.swift +52 -0
  89. package/nitrogen/generated/ios/swift/MultipeerDiscoveryInfoEntry.swift +34 -0
  90. package/nitrogen/generated/ios/swift/MultipeerEncryptionPreference.swift +44 -0
  91. package/nitrogen/generated/ios/swift/MultipeerPeer.swift +63 -0
  92. package/nitrogen/generated/ios/swift/MultipeerPeerState.swift +44 -0
  93. package/nitrogen/generated/ios/swift/MultipeerSessionOptions.swift +136 -0
  94. package/nitrogen/generated/ios/swift/PhyStatus.swift +34 -0
  95. package/nitrogen/generated/shared/c++/BluetoothCapabilities.hpp +131 -0
  96. package/nitrogen/generated/shared/c++/BluetoothPhy.hpp +80 -0
  97. package/nitrogen/generated/shared/c++/BluetoothPhyOption.hpp +80 -0
  98. package/nitrogen/generated/shared/c++/BondState.hpp +84 -0
  99. package/nitrogen/generated/shared/c++/DescriptorValue.hpp +95 -0
  100. package/nitrogen/generated/shared/c++/ExtendedAdvertisingOptions.hpp +138 -0
  101. package/nitrogen/generated/shared/c++/GATTCharacteristic.hpp +9 -3
  102. package/nitrogen/generated/shared/c++/GATTDescriptor.hpp +93 -0
  103. package/nitrogen/generated/shared/c++/GATTService.hpp +7 -2
  104. package/nitrogen/generated/shared/c++/HybridMunimBluetoothSpec.cpp +29 -0
  105. package/nitrogen/generated/shared/c++/HybridMunimBluetoothSpec.hpp +61 -2
  106. package/nitrogen/generated/shared/c++/L2CAPChannel.hpp +92 -0
  107. package/nitrogen/generated/shared/c++/MultipeerDiscoveryInfoEntry.hpp +87 -0
  108. package/nitrogen/generated/shared/c++/MultipeerEncryptionPreference.hpp +80 -0
  109. package/nitrogen/generated/shared/c++/MultipeerPeer.hpp +102 -0
  110. package/nitrogen/generated/shared/c++/MultipeerPeerState.hpp +80 -0
  111. package/nitrogen/generated/shared/c++/MultipeerSessionOptions.hpp +114 -0
  112. package/nitrogen/generated/shared/c++/PhyStatus.hpp +88 -0
  113. package/package.json +22 -11
  114. package/src/index.ts +416 -31
  115. package/src/specs/munim-bluetooth.nitro.ts +298 -14
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  <a aria-label="Package version" href="https://www.npmjs.com/package/munim-bluetooth" target="_blank">
12
12
  <img alt="Package version" src="https://img.shields.io/npm/v/munim-bluetooth.svg?style=flat-square&label=Version&labelColor=000000&color=0066CC" />
13
13
  </a>
14
- <a aria-label="Package is free to use" href="https://github.com/munimtechnologies/munim-bluetooth/blob/main/LICENSE" target="_blank">
14
+ <a aria-label="Package is free to use" href="https://github.com/munimtechnologies/munim-bluetooth/blob/master/LICENSE" target="_blank">
15
15
  <img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-success.svg?style=flat-square&color=33CC12" target="_blank" />
16
16
  </a>
17
17
  <a aria-label="package downloads" href="https://www.npmtrends.com/munim-bluetooth" target="_blank">
@@ -45,13 +45,13 @@
45
45
 
46
46
  ## Introduction
47
47
 
48
- **munim-bluetooth** is a comprehensive React Native library for all your Bluetooth Low Energy (BLE) needs, supporting both peripheral and central roles. This library allows your React Native app to act as a BLE peripheral (advertising services and characteristics) or as a BLE central (scanning, connecting, and communicating with devices).
48
+ **munim-bluetooth** is a comprehensive React Native Bluetooth library for BLE central/peripheral work, Android-only Classic Bluetooth APIs, LE L2CAP channels where the OS exposes them, and Apple Multipeer Connectivity for iOS/iPadOS peer messaging. It lets your React Native app advertise services, scan, connect, read, write, subscribe, exchange nearby peer messages, and check platform capabilities before using optional APIs.
49
49
 
50
50
  **Fully compatible with Expo!** Works seamlessly with both Expo managed and bare workflows.
51
51
 
52
52
  **Built with React Native's Nitro modules architecture** for high performance and reliability.
53
53
 
54
- **Note**: This library focuses on reliability and platform compatibility. It supports the core BLE features that work consistently across both Android and iOS platforms.
54
+ **Note**: Bluetooth is heavily platform-gated. This library exposes the features that iOS and Android make available to third-party apps, and it reports unsupported OS-level capabilities through `getCapabilities()` or explicit unsupported errors instead of silently pretending they work.
55
55
 
56
56
  ## Table of contents
57
57
 
@@ -81,7 +81,7 @@
81
81
  - 🔵 **BLE Peripheral Mode**: Transform your React Native app into a BLE peripheral device
82
82
  - 📡 **Service Advertising**: Advertise custom GATT services with multiple characteristics
83
83
  - 🔄 **Real-time Communication**: Support for read, write, and notify operations
84
- - ✅ **Platform-Supported BLE Advertising**: Support for core BLE advertising data types that work reliably on both platforms
84
+ - ✅ **Platform-Aware BLE Advertising**: Use service UUIDs and local names cross-platform, plus Android advertising payload data where the OS allows it
85
85
  - 🔧 **Dynamic Updates**: Update advertising data while advertising is active
86
86
 
87
87
  ### Central Mode
@@ -95,11 +95,131 @@
95
95
  ### Additional Features
96
96
 
97
97
  - 📱 **Cross-platform**: Works on both iOS and Android
98
+ - 🧭 **Capability Reporting**: `getCapabilities()` reports platform and hardware support before you call optional APIs
99
+ - 🕸️ **Apple Multipeer Transport**: iOS/iPadOS devices can discover, invite, and message nearby peers with Apple's Multipeer Connectivity
100
+ - 🧵 **LE L2CAP Channels**: Stream payloads over LE Credit Based Channels on supported iOS and Android versions
101
+ - 🔌 **Android Classic Bluetooth**: Android RFCOMM client/server messaging for SPP-style devices
98
102
  - 🎯 **TypeScript Support**: Full TypeScript definitions included
99
103
  - ⚡ **High Performance**: Built with React Native's Nitro modules architecture
100
104
  - 🚀 **Expo Compatible**: Works seamlessly with Expo managed and bare workflows
101
105
  - 🔐 **Permission Handling**: Built-in permission request helpers
102
106
 
107
+ ## Platform Support Matrix
108
+
109
+ | Capability | iOS | Android | Notes |
110
+ | --- | --- | --- | --- |
111
+ | Peripheral advertising | ✅ | ✅ | iOS only allows CoreBluetooth-supported advertising keys such as local name and service UUIDs. Android splits primary advertising data and scan response data to stay within BLE size limits. |
112
+ | Peripheral GATT services | ✅ | ✅ | Read and write requests are handled natively on both platforms. Included services are wired when supplied in `setServices()`. |
113
+ | Peripheral notify/indicate subscriptions | ✅ | ✅ | Subscribe/unsubscribe events are emitted when centrals change CCC state. |
114
+ | Central scan | ✅ | ✅ | Android scan failures emit `scanFailed`. |
115
+ | Central connect/disconnect | ✅ | ✅ | `connect()` has a native 15 second timeout. |
116
+ | Central service discovery | ✅ | ✅ | Emits `servicesDiscovered` in addition to resolving the Promise. Native timeout rejects if callbacks do not arrive. |
117
+ | Central characteristic read | ✅ | ✅ | Resolves with hex-encoded values. Native timeout rejects if callbacks do not arrive. |
118
+ | Central characteristic write | ✅ | ✅ | Supports `write` and `writeWithoutResponse`. With-response writes have native timeout protection. |
119
+ | Central descriptor read/write | ✅ | ✅ | Uses `readDescriptor()` and `writeDescriptor()` with hex-encoded values. Native timeout rejects if callbacks do not arrive. |
120
+ | Central notify/indicate subscription | ✅ | ✅ | Values emit through `characteristicValueChanged`. |
121
+ | RSSI read | ✅ | ✅ | Resolves with dBm. |
122
+ | ATT MTU request | ❌ | ✅ | Android supports `requestMTU()`. iOS negotiates ATT MTU internally and does not expose a public setter. |
123
+ | BLE PHY read/preference | ❌ | ✅ | Android 8+ supports `readPhy()` and `setPreferredPhy()` when hardware allows it. |
124
+ | Pairing/bond state | ❌ | ✅ | Android supports bond state and starts/removes bonds. iOS handles pairing automatically and does not expose bond management through CoreBluetooth. |
125
+ | Extended advertising | ❌ | ✅ | Android 8+ supports `startExtendedAdvertising()` on hardware with LE extended advertising. iOS does not expose BLE extended advertising. |
126
+ | BLE L2CAP channel streams | ✅ | ✅ | iOS uses CoreBluetooth LE Credit Based Channels. Android requires Android 10+ for LE CoC sockets. |
127
+ | Classic Bluetooth RFCOMM | ❌ | ✅ | Android supports discovery, SPP-style RFCOMM client connections, server/listener sockets, disconnect, write, and receive events. iOS apps cannot use public Classic Bluetooth RFCOMM APIs. |
128
+ | Apple Multipeer Connectivity | ✅ | ❌ | iOS/iPadOS devices can discover peers, auto-invite/accept sessions, and exchange encrypted messages. Android cannot join Apple's Multipeer sessions; use BLE/GATT for iOS-to-Android. |
129
+
130
+ Call `getCapabilities()` at runtime when you need optional behavior. Platform support can still vary by OS version, hardware, permissions, and app background state.
131
+
132
+ ## Device-to-Device Messaging
133
+
134
+ For phone-to-phone apps, treat advertising as discovery and GATT as the reliable message channel:
135
+
136
+ - A peripheral advertises one or more service UUIDs, defines writable/readable/notifiable characteristics with `setServices()`, and listens for `peripheralReadRequest`, `peripheralWriteRequest`, `peripheralSubscribed`, and `peripheralUnsubscribed`.
137
+ - A central scans for that service, connects, discovers services, reads or writes the characteristic, and subscribes for notifications through `characteristicValueChanged`.
138
+ - Multiple nearby centrals can connect, write, and subscribe at the same time. Track peers by `deviceId` on the central side and `centralId` on the peripheral side.
139
+
140
+ Advertising payload caveat: Android can advertise manufacturer data, service data, TX power, appearance, and related fields subject to BLE payload size and hardware limits. iOS public CoreBluetooth peripheral advertising only exposes local name and service UUIDs, so arbitrary relay bytes should go in a GATT characteristic for iOS-to-iOS and iOS-to-Android communication. If you need a tiny discovery hint on iOS, encode it into your advertised service UUID choice or local name with the normal privacy and size tradeoffs.
141
+
142
+ ### Apple Multipeer Connectivity
143
+
144
+ For iOS-to-iOS or iPadOS-to-iOS communication, `startMultipeerSession()` exposes Apple's Multipeer Connectivity as a higher-level peer transport. It advertises and browses using a Bonjour service type, auto-invites discovered peers by default, accepts incoming invitations by default, and sends hex-encoded payloads to one peer or all connected peers.
145
+
146
+ ```typescript
147
+ import {
148
+ addEventListener,
149
+ sendMultipeerMessage,
150
+ startMultipeerSession,
151
+ stopMultipeerSession,
152
+ } from 'munim-bluetooth'
153
+
154
+ startMultipeerSession({
155
+ serviceType: 'munim-mesh',
156
+ displayName: 'Sheehan iPhone',
157
+ discoveryInfo: [{ key: 'role', value: 'wallet-peer' }],
158
+ autoInvite: true,
159
+ autoAcceptInvitations: true,
160
+ encryptionPreference: 'required',
161
+ })
162
+
163
+ addEventListener('multipeerPeerStateChanged', async (peer) => {
164
+ if (peer.state === 'connected') {
165
+ await sendMultipeerMessage('68656c6c6f', [peer.id], true)
166
+ }
167
+ })
168
+
169
+ addEventListener('multipeerMessageReceived', ({ displayName, value }) => {
170
+ console.log('message from', displayName, value)
171
+ })
172
+
173
+ // Later:
174
+ stopMultipeerSession()
175
+ ```
176
+
177
+ Multipeer service types must be 1-15 lowercase letters/numbers/hyphens, and the matching Bonjour entry must be declared in iOS `Info.plist` as `_<serviceType>._tcp` (for example `_munim-mesh._tcp`). The Expo config plugin adds `_munim-mesh._tcp` by default and accepts a `multipeerServiceTypes` option for custom service types.
178
+
179
+ ## Background and Terminated Behavior
180
+
181
+ `startBackgroundSession()` starts a best-effort BLE session for apps that need nearby communication after the user leaves the app.
182
+
183
+ | State | iOS | Android |
184
+ | --- | --- | --- |
185
+ | App in background or suspended | Supported when `UIBackgroundModes` includes `bluetooth-central` and/or `bluetooth-peripheral`. The Expo config plugin adds both by default. | Supported through a foreground service with `connectedDevice` service type and a user-visible notification. |
186
+ | App terminated by the system | Best-effort CoreBluetooth state restoration is enabled when background modes are present. The package uses restoration identifiers and emits `backgroundSessionRestored` when CoreBluetooth restores central/peripheral state. On iOS 26 and later, Apple's Bluetooth relaunch rules require AccessorySetupKit eligibility for background relaunch, so arbitrary phone-to-phone BLE mesh apps should not depend on terminated-state relaunch. | The foreground service persists its session config and uses `START_STICKY`; if the process is recreated, it restores scan, advertising, and a native GATT characteristic store from the services configured with `setServices()`. |
187
+ | User force-quits / force-stops the app | Not supported by iOS for ongoing app-owned BLE work. The user has explicitly stopped the app. | Not supported after Android force stop. The OS prevents the app from running again until the user opens it or another allowed user/system action starts it. |
188
+
189
+ Background sessions are for keeping discovery and small GATT messages alive. They do not make JavaScript execute indefinitely. If the process is alive, normal JS events such as `peripheralWriteRequest` and `characteristicValueChanged` continue. After a system restart, iOS may relaunch the app only when Apple's current CoreBluetooth restoration rules allow it; Android restores native scan/advertise/GATT state in the foreground service, and app-specific business logic should reconcile state when the app opens again.
190
+
191
+ ```typescript
192
+ import {
193
+ setServices,
194
+ startBackgroundSession,
195
+ stopBackgroundSession,
196
+ } from 'munim-bluetooth'
197
+
198
+ setServices([
199
+ {
200
+ uuid: SERVICE_UUID,
201
+ characteristics: [
202
+ {
203
+ uuid: CHARACTERISTIC_UUID,
204
+ properties: ['read', 'write', 'writeWithoutResponse', 'notify'],
205
+ value: '70696e67',
206
+ },
207
+ ],
208
+ },
209
+ ])
210
+
211
+ startBackgroundSession({
212
+ serviceUUIDs: [SERVICE_UUID],
213
+ localName: 'MunimPeer',
214
+ scanMode: 'lowPower',
215
+ androidNotificationTitle: 'Nearby mode',
216
+ androidNotificationText: 'Keeping Bluetooth available nearby',
217
+ })
218
+
219
+ // Later:
220
+ stopBackgroundSession()
221
+ ```
222
+
103
223
  ## 📦 Installation
104
224
 
105
225
  ### React Native CLI
@@ -117,6 +237,8 @@ npx expo install munim-bluetooth react-native-nitro-modules
117
237
  ```
118
238
 
119
239
  > **Note**: This library requires Expo SDK 50+ and works with both managed and bare workflows. To support Nitro modules, you need React Native version v0.78.0 or higher.
240
+ >
241
+ > **Important**: This package requires a native development build in Expo. It does not work in Expo Go. After installing, run `npx expo run:ios`, `npx expo run:android`, or create a development build with EAS.
120
242
 
121
243
  ### iOS Setup
122
244
 
@@ -127,6 +249,12 @@ For iOS, the library is automatically linked. However, you need to add the follo
127
249
  <string>This app uses Bluetooth for BLE communication</string>
128
250
  <key>NSBluetoothPeripheralUsageDescription</key>
129
251
  <string>This app uses Bluetooth to create a peripheral device</string>
252
+ <key>NSLocalNetworkUsageDescription</key>
253
+ <string>This app uses the local network to discover and communicate with nearby peer devices</string>
254
+ <key>NSBonjourServices</key>
255
+ <array>
256
+ <string>_munim-mesh._tcp</string>
257
+ </array>
130
258
  ```
131
259
 
132
260
  **For Expo projects**, add these permissions to your `app.json`:
@@ -137,13 +265,33 @@ For iOS, the library is automatically linked. However, you need to add the follo
137
265
  "ios": {
138
266
  "infoPlist": {
139
267
  "NSBluetoothAlwaysUsageDescription": "This app uses Bluetooth for BLE communication",
140
- "NSBluetoothPeripheralUsageDescription": "This app uses Bluetooth to create a peripheral device"
268
+ "NSBluetoothPeripheralUsageDescription": "This app uses Bluetooth to create a peripheral device",
269
+ "NSLocalNetworkUsageDescription": "This app uses the local network to discover and communicate with nearby peer devices",
270
+ "NSBonjourServices": ["_munim-mesh._tcp"]
141
271
  }
142
272
  }
143
273
  }
144
274
  }
145
275
  ```
146
276
 
277
+ With the included Expo config plugin, the default `munim-mesh` Multipeer service is declared automatically. For custom service types:
278
+
279
+ ```json
280
+ {
281
+ "expo": {
282
+ "plugins": [
283
+ [
284
+ "munim-bluetooth",
285
+ {
286
+ "multipeerServiceTypes": ["anonmesh", "munim-mesh"],
287
+ "localNetworkUsageDescription": "This app discovers nearby private wallet peers."
288
+ }
289
+ ]
290
+ ]
291
+ }
292
+ }
293
+ ```
294
+
147
295
  ### Android Setup
148
296
 
149
297
  For Android, add the following permissions to your `AndroidManifest.xml`:
@@ -189,7 +337,6 @@ import { startAdvertising, stopAdvertising, setServices } from 'munim-bluetooth'
189
337
  startAdvertising({
190
338
  serviceUUIDs: ['180D', '180F'],
191
339
  localName: 'My Device',
192
- manufacturerData: '0102030405',
193
340
  })
194
341
 
195
342
  // Set GATT services
@@ -199,8 +346,8 @@ setServices([
199
346
  characteristics: [
200
347
  {
201
348
  uuid: '2A37',
202
- properties: ['read', 'notify'],
203
- value: 'Hello World',
349
+ properties: ['read', 'write', 'writeWithoutResponse', 'notify'],
350
+ value: '48656c6c6f20576f726c64',
204
351
  },
205
352
  ],
206
353
  },
@@ -214,6 +361,9 @@ stopAdvertising()
214
361
 
215
362
  ```typescript
216
363
  import {
364
+ addDeviceFoundListener,
365
+ isBluetoothEnabled,
366
+ requestBluetoothPermission,
217
367
  startScan,
218
368
  stopScan,
219
369
  connect,
@@ -222,27 +372,80 @@ import {
222
372
  subscribeToCharacteristic,
223
373
  } from 'munim-bluetooth'
224
374
 
225
- // Start scanning
375
+ const hasPermission = await requestBluetoothPermission()
376
+ if (!hasPermission) {
377
+ throw new Error('Bluetooth permission was not granted')
378
+ }
379
+
380
+ const enabled = await isBluetoothEnabled()
381
+ if (!enabled) {
382
+ throw new Error('Bluetooth is turned off')
383
+ }
384
+
385
+ const removeDeviceFoundListener = addDeviceFoundListener((device) => {
386
+ console.log('Found device:', device.id, device.name)
387
+ })
388
+
226
389
  startScan({
227
390
  serviceUUIDs: ['180D'],
228
391
  allowDuplicates: false,
229
392
  scanMode: 'balanced',
230
393
  })
231
394
 
232
- // Connect to a device (deviceId from deviceFound event)
395
+ // Later, after choosing a discovered device ID:
233
396
  await connect('device-id-here')
234
-
235
- // Discover services
236
397
  const services = await discoverServices('device-id-here')
237
-
238
- // Read a characteristic
239
398
  const value = await readCharacteristic('device-id-here', '180D', '2A37')
240
-
241
- // Subscribe to notifications
242
399
  subscribeToCharacteristic('device-id-here', '180D', '2A37')
400
+
401
+ // Cleanup when finished scanning
402
+ stopScan()
403
+ removeDeviceFoundListener()
404
+ ```
405
+
406
+ ### Peripheral Write and Subscribe Events
407
+
408
+ ```typescript
409
+ import {
410
+ addEventListener,
411
+ setServices,
412
+ startAdvertising,
413
+ updateCharacteristicValue,
414
+ } from 'munim-bluetooth'
415
+
416
+ const SERVICE_UUID = '71f271d0-8f4c-4c4d-8a2d-6f3a9497b41d'
417
+ const CHARACTERISTIC_UUID = '4ad4a6d2-3f4a-477c-9832-5e0d8f7654d8'
418
+
419
+ setServices([
420
+ {
421
+ uuid: SERVICE_UUID,
422
+ characteristics: [
423
+ {
424
+ uuid: CHARACTERISTIC_UUID,
425
+ properties: ['read', 'write', 'writeWithoutResponse', 'notify'],
426
+ value: '70696e67',
427
+ },
428
+ ],
429
+ },
430
+ ])
431
+
432
+ addEventListener('peripheralWriteRequest', ({ centralId, value }) => {
433
+ console.log('Peer wrote', centralId, value)
434
+ updateCharacteristicValue(SERVICE_UUID, CHARACTERISTIC_UUID, value, true)
435
+ })
436
+
437
+ addEventListener('peripheralSubscribed', ({ centralId }) => {
438
+ console.log('Peer subscribed', centralId)
439
+ updateCharacteristicValue(SERVICE_UUID, CHARACTERISTIC_UUID, '706f6e67', true)
440
+ })
441
+
442
+ startAdvertising({
443
+ serviceUUIDs: [SERVICE_UUID],
444
+ localName: 'MunimPeer',
445
+ })
243
446
  ```
244
447
 
245
- ### Advanced Usage with Supported Advertising Data Types
448
+ ### Advanced Usage with Android Advertising Data Types
246
449
 
247
450
  ```typescript
248
451
  import {
@@ -252,27 +455,28 @@ import {
252
455
  type AdvertisingDataTypes,
253
456
  } from 'munim-bluetooth'
254
457
 
255
- // Platform-supported advertising data configuration
458
+ // Android advertising data configuration. iOS peripheral advertising only
459
+ // broadcasts service UUIDs/local name through public CoreBluetooth APIs.
256
460
  const advertisingData: AdvertisingDataTypes = {
257
461
  // 0x01 - Flags (LE General Discoverable Mode, BR/EDR Not Supported)
258
462
  flags: 0x06,
259
463
 
260
- // 0x02-0x07 - Service UUIDs (fully supported)
464
+ // 0x02-0x07 - Service UUIDs
261
465
  completeServiceUUIDs16: ['180D', '180F'],
262
466
  incompleteServiceUUIDs128: ['0000180D-0000-1000-8000-00805F9B34FB'],
263
467
 
264
- // 0x08-0x09 - Local Name (fully supported)
468
+ // 0x08-0x09 - Local Name
265
469
  completeLocalName: 'My Smart Device',
266
470
  shortenedLocalName: 'SmartDev',
267
471
 
268
- // 0x0A - Tx Power Level (fully supported)
472
+ // 0x0A - Tx Power Level
269
473
  txPowerLevel: -12,
270
474
 
271
- // 0x14-0x15 - Service Solicitation (fully supported)
475
+ // 0x14-0x15 - Service Solicitation
272
476
  serviceSolicitationUUIDs16: ['180D'],
273
477
  serviceSolicitationUUIDs128: ['0000180D-0000-1000-8000-00805F9B34FB'],
274
478
 
275
- // 0x16, 0x20, 0x21 - Service Data (fully supported)
479
+ // 0x16, 0x20, 0x21 - Service Data
276
480
  serviceData16: [
277
481
  { uuid: '180D', data: '0102030405' },
278
482
  { uuid: '180F', data: '060708090A' },
@@ -284,14 +488,14 @@ const advertisingData: AdvertisingDataTypes = {
284
488
  // 0x19 - Appearance (partial support)
285
489
  appearance: 0x03c0, // Generic Watch
286
490
 
287
- // 0x1F - Service Solicitation (32-bit) (fully supported)
491
+ // 0x1F - Service Solicitation (32-bit)
288
492
  serviceSolicitationUUIDs32: ['0000180D'],
289
493
 
290
- // 0xFF - Manufacturer Specific Data (fully supported)
494
+ // 0xFF - Manufacturer Specific Data
291
495
  manufacturerData: '4C000215FDA50693A4E24FB1AFCFC6EB0764782500010001C5',
292
496
  }
293
497
 
294
- // Start advertising with supported data
498
+ // Start advertising with Android payload data and cross-platform service UUIDs.
295
499
  startAdvertising({
296
500
  serviceUUIDs: ['180D', '180F'],
297
501
  advertisingData: advertisingData,
@@ -322,8 +526,8 @@ Starts BLE advertising with the specified options.
322
526
  - `options` (object):
323
527
  - `serviceUUIDs` (string[]): Array of service UUIDs to advertise
324
528
  - `localName?` (string): Device name (legacy support)
325
- - `manufacturerData?` (string): Manufacturer data in hex format (legacy support)
326
- - `advertisingData?` (AdvertisingDataTypes): Platform-supported advertising data
529
+ - `manufacturerData?` (string): Manufacturer data in hex format (legacy Android advertising support)
530
+ - `advertisingData?` (AdvertisingDataTypes): Platform-aware advertising data. Android can advertise payload fields; iOS advertises local name and service UUIDs.
327
531
 
328
532
  #### `updateAdvertisingData(advertisingData)`
329
533
 
@@ -351,6 +555,10 @@ Sets GATT services and characteristics.
351
555
 
352
556
  - `services` (array): Array of service objects
353
557
 
558
+ #### `updateCharacteristicValue(serviceUUID, characteristicUUID, value, notify)`
559
+
560
+ Updates a local peripheral characteristic value. When `notify` is `true`, the new hex-encoded value is pushed to subscribed centrals using notify/indicate where the characteristic supports it.
561
+
354
562
  ### Central Functions
355
563
 
356
564
  #### `isBluetoothEnabled()`
@@ -365,6 +573,58 @@ Requests Bluetooth permissions (Android) or checks authorization status (iOS).
365
573
 
366
574
  **Returns:** Promise<boolean>
367
575
 
576
+ #### `getCapabilities()`
577
+
578
+ Returns the platform/device Bluetooth feature set.
579
+
580
+ **Returns:** Promise<BluetoothCapabilities>
581
+
582
+ #### `startBackgroundSession(options)`
583
+
584
+ Starts a best-effort background BLE session. Android starts a foreground service that restores scan, advertising, and configured GATT services after normal process recreation. iOS keeps CoreBluetooth managers configured with state restoration identifiers when Bluetooth background modes are present; terminated-state relaunch is still subject to Apple's CoreBluetooth relaunch rules, including the iOS 26 AccessorySetupKit restriction.
585
+
586
+ **Parameters:**
587
+
588
+ - `serviceUUIDs` (string[]): Service UUIDs to advertise and scan for.
589
+ - `localName?` (string): Local name to advertise where supported.
590
+ - `allowDuplicates?` (boolean): Whether scan callbacks may repeat the same device.
591
+ - `scanMode?` ('lowPower' | 'balanced' | 'lowLatency'): Android scan mode preference.
592
+ - `androidNotificationChannelId?`, `androidNotificationChannelName?`, `androidNotificationTitle?`, `androidNotificationText?`: Android foreground service notification options.
593
+
594
+ #### `stopBackgroundSession()`
595
+
596
+ Stops the active background BLE session and clears persisted Android service restore state.
597
+
598
+ #### `startMultipeerSession(options)`
599
+
600
+ Starts Apple Multipeer Connectivity discovery and messaging on iOS/iPadOS.
601
+
602
+ **Parameters:**
603
+
604
+ - `serviceType` (string): Bonjour service type, 1-15 lowercase letters/numbers/hyphens.
605
+ - `displayName?` (string): Name shown to nearby peers.
606
+ - `discoveryInfo?` (`{ key: string; value: string }[]`): Small discovery metadata.
607
+ - `autoInvite?` (boolean): Automatically invite discovered peers. Defaults to `true`.
608
+ - `autoAcceptInvitations?` (boolean): Automatically accept incoming invitations. Defaults to `true`.
609
+ - `inviteTimeout?` (number): Invitation timeout in seconds. Defaults to `30`.
610
+ - `encryptionPreference?` (`'none' | 'optional' | 'required'`): Defaults to `required`.
611
+
612
+ #### `stopMultipeerSession()`
613
+
614
+ Stops the local Multipeer advertiser, browser, and session.
615
+
616
+ #### `inviteMultipeerPeer(peerId)`
617
+
618
+ Invites a discovered Multipeer peer when `autoInvite` is disabled or you want manual control.
619
+
620
+ #### `getMultipeerPeers()`
621
+
622
+ Returns discovered and connected Multipeer peers for the active runtime session.
623
+
624
+ #### `sendMultipeerMessage(value, peerIds?, reliable?)`
625
+
626
+ Sends a hex-encoded payload to connected Multipeer peers. Omit `peerIds` to broadcast to every connected peer. `reliable` defaults to `true`.
627
+
368
628
  #### `startScan(options?)`
369
629
 
370
630
  Starts scanning for BLE devices.
@@ -388,7 +648,7 @@ Connects to a BLE device.
388
648
 
389
649
  - `deviceId` (string): The unique identifier of the device
390
650
 
391
- **Returns:** Promise<void>
651
+ **Returns:** Promise<void>. The promise rejects if the native connection does not complete within 15 seconds.
392
652
 
393
653
  #### `disconnect(deviceId)`
394
654
 
@@ -406,7 +666,7 @@ Discovers GATT services for a connected device.
406
666
 
407
667
  - `deviceId` (string): The unique identifier of the connected device
408
668
 
409
- **Returns:** Promise<GATTService[]>
669
+ **Returns:** Promise<GATTService[]>. The promise rejects if native service discovery does not complete within 15 seconds.
410
670
 
411
671
  #### `readCharacteristic(deviceId, serviceUUID, characteristicUUID)`
412
672
 
@@ -418,7 +678,13 @@ Reads a characteristic value from a connected device.
418
678
  - `serviceUUID` (string): The UUID of the service
419
679
  - `characteristicUUID` (string): The UUID of the characteristic
420
680
 
421
- **Returns:** Promise<CharacteristicValue>
681
+ **Returns:** Promise<CharacteristicValue>. The promise rejects if the native read callback does not arrive within 15 seconds.
682
+
683
+ #### `readDescriptor(deviceId, serviceUUID, characteristicUUID, descriptorUUID)`
684
+
685
+ Reads a descriptor value from a connected device.
686
+
687
+ **Returns:** Promise<DescriptorValue>. The promise rejects if descriptor discovery/read callbacks do not arrive within 15 seconds.
422
688
 
423
689
  #### `writeCharacteristic(deviceId, serviceUUID, characteristicUUID, value, writeType?)`
424
690
 
@@ -432,7 +698,13 @@ Writes a value to a characteristic on a connected device.
432
698
  - `value` (string): The value to write (hex string)
433
699
  - `writeType?` ('write' | 'writeWithoutResponse'): Write type
434
700
 
435
- **Returns:** Promise<void>
701
+ **Returns:** Promise<void>. With-response writes reject if the native write callback does not arrive within 15 seconds.
702
+
703
+ #### `writeDescriptor(deviceId, serviceUUID, characteristicUUID, descriptorUUID, value)`
704
+
705
+ Writes a descriptor value to a connected device.
706
+
707
+ **Returns:** Promise<void>. The promise rejects if descriptor discovery/write callbacks do not arrive within 15 seconds.
436
708
 
437
709
  #### `subscribeToCharacteristic(deviceId, serviceUUID, characteristicUUID)`
438
710
 
@@ -454,6 +726,44 @@ Unsubscribes from notifications/indications from a characteristic.
454
726
  - `serviceUUID` (string): The UUID of the service
455
727
  - `characteristicUUID` (string): The UUID of the characteristic
456
728
 
729
+ ### Events
730
+
731
+ Use `addEventListener(eventName, callback)` for BLE status and data events.
732
+
733
+ | Event | Payload |
734
+ | --- | --- |
735
+ | `deviceFound` | Discovered BLE device payload: `{ id, name?, localName?, rssi?, serviceUUIDs?, serviceData?, manufacturerData?, txPowerLevel?, isConnectable?, advertisingData? }`. |
736
+ | `onDeviceFound`, `scanResult` | Legacy aliases for `deviceFound`. |
737
+ | `scanFailed` | `{ errorCode, message }` on Android scan callback failure. |
738
+ | `advertisingStarted` | Empty payload when advertising starts. |
739
+ | `advertisingStartFailed` | Android: `{ errorCode, message }`; iOS: `{ error }`. |
740
+ | `classicDeviceFound` | Android Classic discovery result: `{ id, name, bondState }`. |
741
+ | `classicScanFailed`, `classicScanFinished` | Android Classic discovery status events. |
742
+ | `classicConnected`, `classicDisconnected` | Android Classic RFCOMM connection status: `{ deviceId }`. |
743
+ | `classicConnectionReceived` | Android Classic RFCOMM inbound connection: `{ deviceId }`. |
744
+ | `classicServerStarted`, `classicServerStopped` | Android Classic RFCOMM listener status. |
745
+ | `classicDataReceived` | Android Classic RFCOMM data: `{ deviceId, value }`. |
746
+ | `deviceConnected` | `{ deviceId }` |
747
+ | `deviceDisconnected` | `{ deviceId }` |
748
+ | `servicesDiscovered` | `{ deviceId, services }` |
749
+ | `characteristicValueChanged` | `{ deviceId, serviceUUID, characteristicUUID, value }` |
750
+ | `l2capChannelPublished`, `l2capChannelUnpublished` | Local LE L2CAP channel lifecycle status. |
751
+ | `l2capChannelOpened`, `l2capChannelClosed` | LE L2CAP stream lifecycle status. |
752
+ | `l2capChannelPublishFailed`, `l2capChannelOpenFailed` | LE L2CAP failure status. |
753
+ | `l2capDataReceived` | LE L2CAP stream data: `{ channelId, psm, deviceId, value }`. |
754
+ | `rssiUpdated` | `{ deviceId, rssi }` |
755
+ | `peripheralReadRequest` | `{ centralId, serviceUUID, characteristicUUID, value }` |
756
+ | `peripheralWriteRequest` | `{ centralId, serviceUUID, characteristicUUID, value }` |
757
+ | `peripheralSubscribed` | `{ centralId, serviceUUID, characteristicUUID }` |
758
+ | `peripheralUnsubscribed` | `{ centralId, serviceUUID, characteristicUUID }` |
759
+ | `backgroundSessionStarted` | `{ platform, serviceUUIDs?, localName? }` |
760
+ | `backgroundSessionStopped` | `{ platform }` |
761
+ | `backgroundSessionRestored` | `{ platform, role?, isScanning?, isAdvertising?, serviceUUIDs?, deviceIds? }` |
762
+ | `backgroundSessionStartFailed` | `{ platform, error }` |
763
+ | `multipeerStarted`, `multipeerStopped`, `multipeerStartFailed` | Apple Multipeer lifecycle status. |
764
+ | `multipeerPeerFound`, `multipeerPeerLost`, `multipeerPeerStateChanged` | Apple Multipeer peer discovery and connection state. |
765
+ | `multipeerMessageReceived` | Apple Multipeer data: `{ peerId, displayName, value }`. |
766
+
457
767
  #### `getConnectedDevices()`
458
768
 
459
769
  Gets list of currently connected devices.
@@ -470,18 +780,72 @@ Reads RSSI (signal strength) for a connected device.
470
780
 
471
781
  **Returns:** Promise<number>
472
782
 
783
+ #### `requestMTU(deviceId, mtu)`
784
+
785
+ Requests an ATT MTU on Android. iOS rejects with an unsupported error because CoreBluetooth negotiates MTU internally.
786
+
787
+ **Returns:** Promise<number>
788
+
789
+ #### `setPreferredPhy(deviceId, txPhy, rxPhy, phyOption?)`
790
+
791
+ Sets preferred BLE PHY on Android 8+ when hardware supports it. iOS rejects with an unsupported error.
792
+
793
+ **Returns:** Promise<void>
794
+
795
+ #### `readPhy(deviceId)`
796
+
797
+ Reads the current BLE PHY on Android 8+ when hardware supports it. iOS rejects with an unsupported error.
798
+
799
+ **Returns:** Promise<PhyStatus>
800
+
801
+ #### `getBondState(deviceId)`
802
+
803
+ Returns Android bond state. iOS resolves to `unsupported`.
804
+
805
+ **Returns:** Promise<BondState>
806
+
807
+ #### `createBond(deviceId)`
808
+
809
+ Starts Android pairing/bonding. iOS rejects with an unsupported error.
810
+
811
+ **Returns:** Promise<BondState>
812
+
813
+ #### `removeBond(deviceId)`
814
+
815
+ Removes an Android bond when the OS exposes that operation. iOS rejects with an unsupported error.
816
+
817
+ **Returns:** Promise<BondState>
818
+
819
+ #### `startExtendedAdvertising(options)`
820
+
821
+ Starts an Android BLE extended advertising set on Android 8+ hardware that supports LE extended advertising. iOS rejects with an unsupported error.
822
+
823
+ **Returns:** Promise<string>
824
+
825
+ #### `stopExtendedAdvertising(advertisingId)`
826
+
827
+ Stops an Android BLE extended advertising set.
828
+
829
+ #### `publishL2CAPChannel()`, `openL2CAPChannel()`, `sendL2CAPData()`
830
+
831
+ Opens BLE L2CAP channel streams. iOS uses CoreBluetooth LE Credit Based Channels. Android requires Android 10+.
832
+
833
+ #### `startClassicScan()`, `connectClassic()`, `startClassicServer()`, `writeClassic()`
834
+
835
+ Android Classic Bluetooth RFCOMM discovery, client connection, server listener, write, disconnect, and receive events. iOS rejects with explicit unsupported errors because public iOS APIs do not expose arbitrary Classic RFCOMM.
836
+
473
837
  ### Types
474
838
 
475
839
  #### `AdvertisingDataTypes`
476
840
 
477
- Platform-supported interface for BLE advertising data types:
841
+ Platform-aware interface for BLE advertising data types. Android can advertise these payload fields when hardware and payload size allow it. iOS can scan many of these fields from other peripherals, but iOS peripheral advertising is limited to local name and service UUIDs.
478
842
 
479
843
  ```typescript
480
844
  interface AdvertisingDataTypes {
481
- // 0x01 - Flags (partial support)
845
+ // 0x01 - Flags
482
846
  flags?: number
483
847
 
484
- // 0x02-0x07 - Service UUIDs (fully supported)
848
+ // 0x02-0x07 - Service UUIDs
485
849
  incompleteServiceUUIDs16?: string[]
486
850
  completeServiceUUIDs16?: string[]
487
851
  incompleteServiceUUIDs32?: string[]
@@ -489,18 +853,18 @@ interface AdvertisingDataTypes {
489
853
  incompleteServiceUUIDs128?: string[]
490
854
  completeServiceUUIDs128?: string[]
491
855
 
492
- // 0x08-0x09 - Local Name (fully supported)
856
+ // 0x08-0x09 - Local Name
493
857
  shortenedLocalName?: string
494
858
  completeLocalName?: string
495
859
 
496
- // 0x0A - Tx Power Level (fully supported)
860
+ // 0x0A - Tx Power Level
497
861
  txPowerLevel?: number
498
862
 
499
- // 0x14-0x15 - Service Solicitation (fully supported)
863
+ // 0x14-0x15 - Service Solicitation
500
864
  serviceSolicitationUUIDs16?: string[]
501
865
  serviceSolicitationUUIDs128?: string[]
502
866
 
503
- // 0x16, 0x20, 0x21 - Service Data (fully supported)
867
+ // 0x16, 0x20, 0x21 - Service Data
504
868
  serviceData16?: Array<{
505
869
  uuid: string
506
870
  data: string
@@ -514,32 +878,32 @@ interface AdvertisingDataTypes {
514
878
  data: string
515
879
  }>
516
880
 
517
- // 0x19 - Appearance (partial support)
881
+ // 0x19 - Appearance
518
882
  appearance?: number
519
883
 
520
- // 0x1F - Service Solicitation (32-bit) (fully supported)
884
+ // 0x1F - Service Solicitation (32-bit)
521
885
  serviceSolicitationUUIDs32?: string[]
522
886
 
523
- // 0xFF - Manufacturer Specific Data (fully supported)
887
+ // 0xFF - Manufacturer Specific Data
524
888
  manufacturerData?: string
525
889
  }
526
890
  ```
527
891
 
528
892
  ## Supported BLE Advertising Data Types
529
893
 
530
- | Hex | Type Name | Description | Support Level | Example |
531
- | ---------------- | ----------------------------- | ------------------------------- | ------------- | ------------------------------------------------- |
532
- | 0x01 | Flags | Basic device capabilities | Partial | `flags: 0x06` |
533
- | 0x02-0x07 | Service UUIDs | Service UUIDs offered | Full | `completeServiceUUIDs16: ['180D']` |
534
- | 0x08-0x09 | Local Name | Device name | Full | `completeLocalName: 'My Device'` |
535
- | 0x0A | Tx Power Level | Transmit power in dBm | Full | `txPowerLevel: -12` |
536
- | 0x14-0x15 | Service Solicitation | Services being sought | Full | `serviceSolicitationUUIDs16: ['180D']` |
537
- | 0x16, 0x20, 0x21 | Service Data | Data associated with services | Full | `serviceData16: [{uuid: '180D', data: '010203'}]` |
538
- | 0x19 | Appearance | Appearance category | Partial | `appearance: 0x03C0` |
539
- | 0x1F | Service Solicitation (32-bit) | 32-bit services being solicited | Full | `serviceSolicitationUUIDs32: ['0000180D']` |
540
- | 0xFF | Manufacturer Specific Data | Vendor-defined data | Full | `manufacturerData: '4748494A4B4C4D4E'` |
894
+ | Hex | Type Name | Description | Advertise Support | Example |
895
+ | ---------------- | ----------------------------- | ------------------------------- | ----------------- | ------------------------------------------------- |
896
+ | 0x01 | Flags | Basic device capabilities | Android | `flags: 0x06` |
897
+ | 0x02-0x07 | Service UUIDs | Service UUIDs offered | iOS + Android | `completeServiceUUIDs16: ['180D']` |
898
+ | 0x08-0x09 | Local Name | Device name | iOS + Android | `completeLocalName: 'My Device'` |
899
+ | 0x0A | Tx Power Level | Transmit power in dBm | Android | `txPowerLevel: -12` |
900
+ | 0x14-0x15 | Service Solicitation | Services being sought | Android | `serviceSolicitationUUIDs16: ['180D']` |
901
+ | 0x16, 0x20, 0x21 | Service Data | Data associated with services | Android | `serviceData16: [{uuid: '180D', data: '010203'}]` |
902
+ | 0x19 | Appearance | Appearance category | Android | `appearance: 0x03C0` |
903
+ | 0x1F | Service Solicitation (32-bit) | 32-bit services being solicited | Android | `serviceSolicitationUUIDs32: ['0000180D']` |
904
+ | 0xFF | Manufacturer Specific Data | Vendor-defined data | Android | `manufacturerData: '4748494A4B4C4D4E'` |
541
905
 
542
- **Note**: This library focuses on reliability and platform compatibility. Advanced BLE features like mesh networking, LE Audio, indoor positioning, etc., are not supported due to platform limitations.
906
+ **Note**: This library focuses on reliable phone-to-phone BLE behavior exposed by public iOS and Android APIs. Bluetooth Mesh, LE Audio broadcast/isoc streams, and arbitrary iOS advertising payloads are not exposed by the mobile OS APIs this package can use.
543
907
 
544
908
  ## 📖 Usage Examples
545
909
 
@@ -548,7 +912,8 @@ interface AdvertisingDataTypes {
548
912
  ```typescript
549
913
  import { startAdvertising, setServices } from 'munim-bluetooth'
550
914
 
551
- // Health device advertising
915
+ // Health device advertising. The payload fields inside advertisingData are
916
+ // advertised on Android; iOS uses serviceUUIDs/localName for advertising.
552
917
  startAdvertising({
553
918
  serviceUUIDs: ['180D', '180F'], // Heart Rate, Battery Service
554
919
  advertisingData: {
@@ -594,7 +959,8 @@ setServices([
594
959
  ```typescript
595
960
  import { startAdvertising, updateAdvertisingData } from 'munim-bluetooth'
596
961
 
597
- // Smart home device
962
+ // Smart home device. The payload fields inside advertisingData are advertised
963
+ // on Android; for iOS peers, put live state in GATT characteristics.
598
964
  startAdvertising({
599
965
  serviceUUIDs: ['1812', '180F'], // HID, Battery Service
600
966
  advertisingData: {
@@ -623,12 +989,11 @@ updateAdvertisingData({
623
989
 
624
990
  ```js
625
991
  import React, { useEffect } from 'react'
992
+ import { Text } from 'react-native'
626
993
  import {
627
994
  startAdvertising,
628
995
  stopAdvertising,
629
996
  setServices,
630
- addListener,
631
- removeListeners,
632
997
  } from 'munim-bluetooth'
633
998
 
634
999
  const MyPeripheral = () => {
@@ -670,7 +1035,6 @@ const MyPeripheral = () => {
670
1035
  // Cleanup on unmount
671
1036
  return () => {
672
1037
  stopAdvertising()
673
- removeListeners('connectionStateChanged')
674
1038
  }
675
1039
  }, [])
676
1040
 
@@ -682,14 +1046,19 @@ const MyPeripheral = () => {
682
1046
 
683
1047
  ```js
684
1048
  import React, { useState, useEffect } from 'react'
1049
+ import { Text, View } from 'react-native'
685
1050
  import {
1051
+ addDeviceFoundListener,
1052
+ addEventListener,
1053
+ disconnect,
1054
+ isBluetoothEnabled,
1055
+ requestBluetoothPermission,
686
1056
  startScan,
687
1057
  stopScan,
688
1058
  connect,
689
1059
  discoverServices,
690
1060
  readCharacteristic,
691
1061
  subscribeToCharacteristic,
692
- addListener,
693
1062
  } from 'munim-bluetooth'
694
1063
 
695
1064
  const DeviceScanner = () => {
@@ -697,25 +1066,61 @@ const DeviceScanner = () => {
697
1066
  const [connectedDevice, setConnectedDevice] = useState(null)
698
1067
 
699
1068
  useEffect(() => {
700
- // Start scanning
701
- startScan({
702
- serviceUUIDs: ['180D'], // Filter by Heart Rate service
703
- allowDuplicates: false,
704
- scanMode: 'balanced',
705
- })
1069
+ let removeDeviceFoundListener = () => {}
1070
+ let removeCharacteristicListener = () => {}
1071
+
1072
+ const start = async () => {
1073
+ const hasPermission = await requestBluetoothPermission()
1074
+ if (!hasPermission) {
1075
+ return
1076
+ }
1077
+
1078
+ const enabled = await isBluetoothEnabled()
1079
+ if (!enabled) {
1080
+ return
1081
+ }
706
1082
 
707
- // Listen for discovered devices
708
- addListener('deviceFound')
709
- // Handle deviceFound events to update devices state
1083
+ removeDeviceFoundListener = addDeviceFoundListener((device) => {
1084
+ setDevices((currentDevices) => {
1085
+ const alreadyExists = currentDevices.some(
1086
+ (currentDevice) => currentDevice.id === device.id
1087
+ )
1088
+
1089
+ if (alreadyExists) {
1090
+ return currentDevices
1091
+ }
1092
+
1093
+ return [...currentDevices, device]
1094
+ })
1095
+ })
1096
+
1097
+ removeCharacteristicListener = addEventListener(
1098
+ 'characteristicValueChanged',
1099
+ (event) => {
1100
+ console.log('Characteristic changed:', event)
1101
+ }
1102
+ )
1103
+
1104
+ startScan({
1105
+ serviceUUIDs: ['180D'], // Filter by Heart Rate service
1106
+ allowDuplicates: false,
1107
+ scanMode: 'balanced',
1108
+ })
1109
+ }
1110
+
1111
+ void start()
710
1112
 
711
1113
  // Cleanup
712
1114
  return () => {
713
1115
  stopScan()
1116
+ removeDeviceFoundListener()
1117
+ removeCharacteristicListener()
1118
+
714
1119
  if (connectedDevice) {
715
1120
  disconnect(connectedDevice)
716
1121
  }
717
1122
  }
718
- }, [])
1123
+ }, [connectedDevice])
719
1124
 
720
1125
  const handleConnect = async (deviceId) => {
721
1126
  await connect(deviceId)
@@ -731,9 +1136,6 @@ const DeviceScanner = () => {
731
1136
 
732
1137
  // Subscribe to notifications
733
1138
  subscribeToCharacteristic(deviceId, '180D', '2A37')
734
-
735
- // Listen for value changes
736
- addListener('characteristicValueChanged')
737
1139
  }
738
1140
 
739
1141
  return (
@@ -757,7 +1159,7 @@ const DeviceScanner = () => {
757
1159
 
758
1160
  ### Expo-Specific Issues
759
1161
 
760
- 1. **Development Build Required**: This library requires a development build in Expo. Use `npx expo run:ios` or `npx expo run:android`
1162
+ 1. **Development Build Required**: This library requires a development build in Expo. Use `npx expo run:ios`, `npx expo run:android`, or an EAS development build. Expo Go is not supported.
761
1163
  2. **Permissions Not Working**: Make sure you've added the permissions to your `app.json` as shown in the setup section
762
1164
  3. **Build Errors**: Ensure you're using Expo SDK 50+ and have the latest Expo CLI
763
1165
  4. **Nitro Modules**: Make sure you have `react-native-nitro-modules` installed and configured