Bluetooth in Bare
How Bare apps talk over Bluetooth Low Energy: the bare-bluetooth module family, central and server roles, GATT versus L2CAP, and where encryption fits.
Bluetooth Low Energy (BLE) lets two devices within radio range find each other and exchange data with no Wi-Fi, no internet and no server. Most peer-to-peer connections in the Pear stack start on a network. Hyperswarm finds peers through the HyperDHT and holepunches a path between them. Bluetooth covers two other cases:
- Places with no connectivity, such as a plane or a crowded venue.
- A second path between peers when the network is available.
This page explains how Bluetooth is put together in Bare.
To run working code, follow the Connect over Bluetooth guides. To avoid the failures that only show up on real devices, read Bluetooth best practices next.
One API over three platform bindings
Each operating system exposes Bluetooth through a different API:
- Apple platforms: CoreBluetooth.
- Android: the Android Bluetooth API.
- Linux: the BlueZ daemon.
Bare has one native binding for each platform, and one module that puts a common API over them:
The modules do the following:
bare-bluetoothpicks a binding through an import map keyed on the platform, sorequire('bare-bluetooth')loads only the binding for the platform it runs on. It unifies class names, state strings and constants, so your code needs no platform conditionals for the common cases. It has no entry for Windows, whererequire()fails.bare-bluetooth-applewraps CoreBluetooth on macOS and iOS. ItsCentralclass wrapsCBCentralManager. Its peripheral class,PeripheralManager, wrapsCBPeripheralManager.bare-bluetooth-androidcalls the Android Bluetooth API through JNI, plus Java helper classes that ship alongside the addon. It needs the app's Java VM, so it runs only inside an app that hosts Bare, such as a Bare Kit worklet.bare-bluetooth-linuxtalks tobluetoothd, the BlueZ daemon, over D-Bus, and handles L2CAP sockets itself.bare-bluetoothwraps it from 0.6.3.
The platform bindings stay available underneath. Their APIs follow each platform's own shape and expose features that bare-bluetooth leaves out. See Use platform-specific Bluetooth features.
Higher up, ble-swarm builds a Hyperswarm-shaped transport on bare-bluetooth. See Security.
Two roles: central and server
Bluetooth Low Energy is asymmetric. The peripheral advertises and waits. The central scans for advertisements and connects. bare-bluetooth names the roles after what your code does with them:
| Role | bare-bluetooth class | Does |
|---|---|---|
| Peripheral | Server | Advertises, holds GATT services, answers reads and writes, publishes L2CAP channels |
| Central | Central | Scans, connects, and talks to the connected device through a Peripheral |
A device can play both roles at once, and a peer-to-peer app usually does: each phone advertises so others can find it, and scans so it can find others. Whoever connects first becomes the central for that connection. The chat in bare-mobile-doctor runs a Central and a Server side by side on every phone for this reason. See Build a Bluetooth chat for iOS and Android.
Two ways to move data
After two devices connect, they can exchange data in two ways:
| GATT | L2CAP channel | |
|---|---|---|
| Shape | Named values: services holding characteristics, each read, written or subscribed to | A byte stream: a Duplex you read and write |
| Size per operation | At most the ATT MTU minus 3 bytes: 20 before the MTU is negotiated up, a few hundred after | Limited only by the stream, and by mtu per write on Linux |
| How the other side finds it | By service and characteristic UUID | By a PSM number that the operating system assigns |
| Talks to | Any BLE device, including standard ones such as heart-rate monitors | Your own app on the other end |
GATT is the common language of BLE: every device speaks it, and standard services have well-known UUIDs. But every value is small, and every operation is a round trip. An L2CAP connection-oriented channel gives you a stream with flow control. Use it for messages, files, or replication.
The catch with L2CAP is the PSM. CoreBluetooth and Android don't let an app choose the number its channel listens on. The operating system assigns one when you call server.publishChannel(), so the other side can't know it in advance. On Linux, publishChannel({ psm }) asks for a specific number, but code that also runs on Apple and Android can't rely on that.
The usual solution combines both mechanisms:
- Advertise a service UUID, so the central can find you.
- Put the PSM in a readable GATT characteristic.
- Open the channel after the central reads the PSM.
bare-mobile-doctor works this way. Hand the PSM over automatically shows the code. ble-swarm also sends the PSM over GATT, but inside a HELLO frame that it notifies on its data characteristic.
Everything is an event
bare-bluetooth has no promises. Each step, from the radio turning on to a value arriving, is an event on one of its objects. Each call starts an operation, and the result comes back as an event:
The calls in this flow are startScan(), connect() and discoverServices(). The reference lists the stateChange, discover and connect events under Central, and servicesDiscover under Peripheral.
Most failures follow the same rule: a failed operation arrives as an error event with a code, not as an exception. This includes:
- A failed connection attempt.
- On Apple and Android, a disconnect that an error causes. It never shows up as
disconnect.
A few failures are thrown instead:
startScan()throws aBluetoothErrorwhen Bluetooth isn't powered on.- The Android binding throws the Java exception when a call itself fails.
- The platform packages throw from calls such as
retrievePeripherals()andgetBondedDevices().
So a Bluetooth module in Bare is a small state machine driven by events, with try/catch around the calls. The BLECentral and BLEServer classes in bare-mobile-doctor follow this shape. Bluetooth best practices collects the rules that keep the state machine honest.
Who is on the other end
A device's id means something different on each platform:
- On Apple, it's a UUID that the system assigns per host. CoreBluetooth never reveals the device's Bluetooth address, so the same phone can show up under different ids on different hosts.
- On Android and Linux, it's the Bluetooth address, such as
AA:BB:CC:DD:EE:FF.
Names are no more reliable. On Android, startAdvertising({ name }) can only switch the phone's own system Bluetooth name on or off. On every platform, a name may arrive late or not at all. So apps identify two things separately:
- What a device is: an advertised service UUID.
- Who it is: something exchanged after connecting, such as a name in the first message (the invite in
bare-mobile-doctor) or a public key (ble-swarm).
Security
Bluetooth offers link-level encryption, but it comes with pairing. You have two options.
Pair the devices and let Bluetooth encrypt the link. server.publishChannel({ encrypted: true }) asks for an encrypted L2CAP channel, which requires the two devices to be paired first.
- On Apple and Android, the operating system runs the pairing and may prompt the user.
- On Linux, there is no system prompt.
bare-bluetoothemits apairRequestevent with aPairRequestfor your app to accept or reject. Only "just works" pairing is supported. - Pairing authenticates devices, not people or apps. It says nothing about which key the app on the other end holds.
Leave the link unencrypted and encrypt on top of it, the way the rest of the Pear stack does. ble-swarm publishes plain channels and runs a Noise handshake over each one. The handshake uses the same kind of key pair as Hyperswarm. Each connection comes out as an encrypted, authenticated stream to a known public key, with no pairing prompt.
- The default
l2cappipe runs the handshake over an L2CAP channel. Thegattpipe runs it over a characteristic instead. - Its connections are the same kind that Hyperswarm emits, so they plug straight into
corestore.replicate(). ble-swarmis experimental upstream, likebare-bluetooth. It runs on macOS 13 and later, iOS and Android. It doesn't yet load the Linux binding.
Common questions
See also
- Connect over Bluetooth—guides built on code that ships upstream.
- Bluetooth best practices—the rules behind those guides.
bare-bluetoothreference—the cross-platform API.- One core, many platforms—where the Bluetooth code sits in a mobile app.
- Hyperswarm—peer discovery over the network.
Last updated on