munim-bluetooth 0.3.26 → 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.
- package/CHANGELOG.md +16 -0
- package/README.md +476 -74
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/cpp/cpp-adapter.cpp +4 -1
- package/android/src/main/java/com/munimbluetooth/BluetoothPermissionUtils.kt +40 -0
- package/android/src/main/java/com/munimbluetooth/HybridMunimBluetooth.kt +2116 -217
- package/android/src/main/java/com/munimbluetooth/MunimBluetoothBackgroundService.kt +591 -56
- package/app.plugin.js +155 -0
- package/ios/HybridMunimBluetooth.swift +2123 -298
- package/ios/MunimBluetoothEventEmitter.swift +68 -8
- package/lib/commonjs/index.js +272 -11
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +243 -11
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +310 -7
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/munim-bluetooth.nitro.d.ts +219 -5
- package/lib/typescript/src/specs/munim-bluetooth.nitro.d.ts.map +1 -1
- package/nitro.json +9 -3
- package/nitrogen/generated/android/c++/JAdvertisingDataTypes.hpp +96 -96
- package/nitrogen/generated/android/c++/JAdvertisingOptions.hpp +8 -8
- package/nitrogen/generated/android/c++/JBackgroundSessionOptions.hpp +8 -8
- package/nitrogen/generated/android/c++/JBluetoothCapabilities.hpp +105 -0
- package/nitrogen/generated/android/c++/JBluetoothPhy.hpp +61 -0
- package/nitrogen/generated/android/c++/JBluetoothPhyOption.hpp +61 -0
- package/nitrogen/generated/android/c++/JBondState.hpp +64 -0
- package/nitrogen/generated/android/c++/JDescriptorValue.hpp +69 -0
- package/nitrogen/generated/android/c++/JExtendedAdvertisingOptions.hpp +131 -0
- package/nitrogen/generated/android/c++/JGATTCharacteristic.hpp +35 -11
- package/nitrogen/generated/android/c++/JGATTDescriptor.hpp +85 -0
- package/nitrogen/generated/android/c++/JGATTService.hpp +33 -9
- package/nitrogen/generated/android/c++/JHybridMunimBluetoothSpec.cpp +422 -12
- package/nitrogen/generated/android/c++/JHybridMunimBluetoothSpec.hpp +29 -0
- package/nitrogen/generated/android/c++/JL2CAPChannel.hpp +66 -0
- package/nitrogen/generated/android/c++/JMultipeerDiscoveryInfoEntry.hpp +61 -0
- package/nitrogen/generated/android/c++/JMultipeerEncryptionPreference.hpp +61 -0
- package/nitrogen/generated/android/c++/JMultipeerPeer.hpp +93 -0
- package/nitrogen/generated/android/c++/JMultipeerPeerState.hpp +61 -0
- package/nitrogen/generated/android/c++/JMultipeerSessionOptions.hpp +105 -0
- package/nitrogen/generated/android/c++/JPhyStatus.hpp +62 -0
- package/nitrogen/generated/android/c++/JScanOptions.hpp +8 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/AdvertisingDataTypes.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/AdvertisingOptions.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BackgroundSessionOptions.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BluetoothCapabilities.kt +111 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BluetoothPhy.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BluetoothPhyOption.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/BondState.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/CharacteristicValue.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/DescriptorValue.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/ExtendedAdvertisingOptions.kt +111 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/GATTCharacteristic.kt +25 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/GATTDescriptor.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/GATTService.kt +23 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/HybridMunimBluetoothSpec.kt +138 -22
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/L2CAPChannel.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerDiscoveryInfoEntry.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerEncryptionPreference.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerPeer.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerPeerState.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/MultipeerSessionOptions.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/PhyStatus.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/ScanOptions.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimbluetooth/ServiceDataEntry.kt +15 -0
- package/nitrogen/generated/ios/MunimBluetooth+autolinking.rb +2 -0
- package/nitrogen/generated/ios/MunimBluetooth-Swift-Cxx-Bridge.cpp +61 -5
- package/nitrogen/generated/ios/MunimBluetooth-Swift-Cxx-Bridge.hpp +494 -49
- package/nitrogen/generated/ios/MunimBluetooth-Swift-Cxx-Umbrella.hpp +42 -0
- package/nitrogen/generated/ios/c++/HybridMunimBluetoothSpecSwift.hpp +254 -0
- package/nitrogen/generated/ios/swift/BluetoothCapabilities.swift +89 -0
- package/nitrogen/generated/ios/swift/BluetoothPhy.swift +44 -0
- package/nitrogen/generated/ios/swift/BluetoothPhyOption.swift +44 -0
- package/nitrogen/generated/ios/swift/BondState.swift +48 -0
- package/nitrogen/generated/ios/swift/DescriptorValue.swift +44 -0
- package/nitrogen/generated/ios/swift/ExtendedAdvertisingOptions.swift +243 -0
- package/nitrogen/generated/ios/swift/Func_void_BluetoothCapabilities.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_BondState.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_DescriptorValue.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_L2CAPChannel.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_PhyStatus.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_MultipeerPeer_.swift +46 -0
- package/nitrogen/generated/ios/swift/GATTCharacteristic.swift +25 -1
- package/nitrogen/generated/ios/swift/GATTDescriptor.swift +71 -0
- package/nitrogen/generated/ios/swift/GATTService.swift +25 -1
- package/nitrogen/generated/ios/swift/HybridMunimBluetoothSpec.swift +29 -0
- package/nitrogen/generated/ios/swift/HybridMunimBluetoothSpec_cxx.swift +556 -23
- package/nitrogen/generated/ios/swift/L2CAPChannel.swift +52 -0
- package/nitrogen/generated/ios/swift/MultipeerDiscoveryInfoEntry.swift +34 -0
- package/nitrogen/generated/ios/swift/MultipeerEncryptionPreference.swift +44 -0
- package/nitrogen/generated/ios/swift/MultipeerPeer.swift +63 -0
- package/nitrogen/generated/ios/swift/MultipeerPeerState.swift +44 -0
- package/nitrogen/generated/ios/swift/MultipeerSessionOptions.swift +136 -0
- package/nitrogen/generated/ios/swift/PhyStatus.swift +34 -0
- package/nitrogen/generated/shared/c++/BluetoothCapabilities.hpp +131 -0
- package/nitrogen/generated/shared/c++/BluetoothPhy.hpp +80 -0
- package/nitrogen/generated/shared/c++/BluetoothPhyOption.hpp +80 -0
- package/nitrogen/generated/shared/c++/BondState.hpp +84 -0
- package/nitrogen/generated/shared/c++/DescriptorValue.hpp +95 -0
- package/nitrogen/generated/shared/c++/ExtendedAdvertisingOptions.hpp +138 -0
- package/nitrogen/generated/shared/c++/GATTCharacteristic.hpp +9 -3
- package/nitrogen/generated/shared/c++/GATTDescriptor.hpp +93 -0
- package/nitrogen/generated/shared/c++/GATTService.hpp +7 -2
- package/nitrogen/generated/shared/c++/HybridMunimBluetoothSpec.cpp +29 -0
- package/nitrogen/generated/shared/c++/HybridMunimBluetoothSpec.hpp +61 -2
- package/nitrogen/generated/shared/c++/L2CAPChannel.hpp +92 -0
- package/nitrogen/generated/shared/c++/MultipeerDiscoveryInfoEntry.hpp +87 -0
- package/nitrogen/generated/shared/c++/MultipeerEncryptionPreference.hpp +80 -0
- package/nitrogen/generated/shared/c++/MultipeerPeer.hpp +102 -0
- package/nitrogen/generated/shared/c++/MultipeerPeerState.hpp +80 -0
- package/nitrogen/generated/shared/c++/MultipeerSessionOptions.hpp +114 -0
- package/nitrogen/generated/shared/c++/PhyStatus.hpp +88 -0
- package/package.json +22 -11
- package/src/index.ts +416 -31
- 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/
|
|
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
|
|
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
|
|
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-
|
|
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: '
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
468
|
+
// 0x08-0x09 - Local Name
|
|
265
469
|
completeLocalName: 'My Smart Device',
|
|
266
470
|
shortenedLocalName: 'SmartDev',
|
|
267
471
|
|
|
268
|
-
// 0x0A - Tx Power Level
|
|
472
|
+
// 0x0A - Tx Power Level
|
|
269
473
|
txPowerLevel: -12,
|
|
270
474
|
|
|
271
|
-
// 0x14-0x15 - Service Solicitation
|
|
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
|
|
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)
|
|
491
|
+
// 0x1F - Service Solicitation (32-bit)
|
|
288
492
|
serviceSolicitationUUIDs32: ['0000180D'],
|
|
289
493
|
|
|
290
|
-
// 0xFF - Manufacturer Specific Data
|
|
494
|
+
// 0xFF - Manufacturer Specific Data
|
|
291
495
|
manufacturerData: '4C000215FDA50693A4E24FB1AFCFC6EB0764782500010001C5',
|
|
292
496
|
}
|
|
293
497
|
|
|
294
|
-
// Start advertising with
|
|
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-
|
|
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-
|
|
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
|
|
845
|
+
// 0x01 - Flags
|
|
482
846
|
flags?: number
|
|
483
847
|
|
|
484
|
-
// 0x02-0x07 - Service UUIDs
|
|
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
|
|
856
|
+
// 0x08-0x09 - Local Name
|
|
493
857
|
shortenedLocalName?: string
|
|
494
858
|
completeLocalName?: string
|
|
495
859
|
|
|
496
|
-
// 0x0A - Tx Power Level
|
|
860
|
+
// 0x0A - Tx Power Level
|
|
497
861
|
txPowerLevel?: number
|
|
498
862
|
|
|
499
|
-
// 0x14-0x15 - Service Solicitation
|
|
863
|
+
// 0x14-0x15 - Service Solicitation
|
|
500
864
|
serviceSolicitationUUIDs16?: string[]
|
|
501
865
|
serviceSolicitationUUIDs128?: string[]
|
|
502
866
|
|
|
503
|
-
// 0x16, 0x20, 0x21 - Service Data
|
|
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
|
|
881
|
+
// 0x19 - Appearance
|
|
518
882
|
appearance?: number
|
|
519
883
|
|
|
520
|
-
// 0x1F - Service Solicitation (32-bit)
|
|
884
|
+
// 0x1F - Service Solicitation (32-bit)
|
|
521
885
|
serviceSolicitationUUIDs32?: string[]
|
|
522
886
|
|
|
523
|
-
// 0xFF - Manufacturer Specific Data
|
|
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
|
|
531
|
-
| ---------------- | ----------------------------- | ------------------------------- |
|
|
532
|
-
| 0x01 | Flags | Basic device capabilities |
|
|
533
|
-
| 0x02-0x07 | Service UUIDs | Service UUIDs offered |
|
|
534
|
-
| 0x08-0x09 | Local Name | Device name |
|
|
535
|
-
| 0x0A | Tx Power Level | Transmit power in dBm |
|
|
536
|
-
| 0x14-0x15 | Service Solicitation | Services being sought |
|
|
537
|
-
| 0x16, 0x20, 0x21 | Service Data | Data associated with services |
|
|
538
|
-
| 0x19 | Appearance | Appearance category |
|
|
539
|
-
| 0x1F | Service Solicitation (32-bit) | 32-bit services being solicited |
|
|
540
|
-
| 0xFF | Manufacturer Specific Data | Vendor-defined data |
|
|
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
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
|
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
|