munim-bluetooth 0.3.27 → 0.4.1

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