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.
- package/CHANGELOG.md +27 -0
- package/LICENSE +201 -21
- package/README.md +480 -75
- 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/HybridMunimBluetooth.kt +2006 -209
- package/android/src/main/java/com/munimbluetooth/MunimBluetoothBackgroundService.kt +561 -53
- 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 +23 -12
- package/src/index.ts +416 -31
- 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:
|
|
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
|
|
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
|
|
|
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-
|
|
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: '
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
471
|
+
// 0x08-0x09 - Local Name
|
|
265
472
|
completeLocalName: 'My Smart Device',
|
|
266
473
|
shortenedLocalName: 'SmartDev',
|
|
267
474
|
|
|
268
|
-
// 0x0A - Tx Power Level
|
|
475
|
+
// 0x0A - Tx Power Level
|
|
269
476
|
txPowerLevel: -12,
|
|
270
477
|
|
|
271
|
-
// 0x14-0x15 - Service Solicitation
|
|
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
|
|
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)
|
|
494
|
+
// 0x1F - Service Solicitation (32-bit)
|
|
288
495
|
serviceSolicitationUUIDs32: ['0000180D'],
|
|
289
496
|
|
|
290
|
-
// 0xFF - Manufacturer Specific Data
|
|
497
|
+
// 0xFF - Manufacturer Specific Data
|
|
291
498
|
manufacturerData: '4C000215FDA50693A4E24FB1AFCFC6EB0764782500010001C5',
|
|
292
499
|
}
|
|
293
500
|
|
|
294
|
-
// Start advertising with
|
|
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-
|
|
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-
|
|
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
|
|
848
|
+
// 0x01 - Flags
|
|
482
849
|
flags?: number
|
|
483
850
|
|
|
484
|
-
// 0x02-0x07 - Service UUIDs
|
|
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
|
|
859
|
+
// 0x08-0x09 - Local Name
|
|
493
860
|
shortenedLocalName?: string
|
|
494
861
|
completeLocalName?: string
|
|
495
862
|
|
|
496
|
-
// 0x0A - Tx Power Level
|
|
863
|
+
// 0x0A - Tx Power Level
|
|
497
864
|
txPowerLevel?: number
|
|
498
865
|
|
|
499
|
-
// 0x14-0x15 - Service Solicitation
|
|
866
|
+
// 0x14-0x15 - Service Solicitation
|
|
500
867
|
serviceSolicitationUUIDs16?: string[]
|
|
501
868
|
serviceSolicitationUUIDs128?: string[]
|
|
502
869
|
|
|
503
|
-
// 0x16, 0x20, 0x21 - Service Data
|
|
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
|
|
884
|
+
// 0x19 - Appearance
|
|
518
885
|
appearance?: number
|
|
519
886
|
|
|
520
|
-
// 0x1F - Service Solicitation (32-bit)
|
|
887
|
+
// 0x1F - Service Solicitation (32-bit)
|
|
521
888
|
serviceSolicitationUUIDs32?: string[]
|
|
522
889
|
|
|
523
|
-
// 0xFF - Manufacturer Specific Data
|
|
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
|
|
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 |
|
|
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
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
|
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
|
|
1184
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
780
1185
|
|
|
781
1186
|
---
|
|
782
1187
|
|