Skip to content

Repository files navigation

capacitor-mesh-ble

Canonical configurable BLE mesh transport for Capacitor apps

The plugin provides Android and iOS radios, chunking, bounded reassembly, relay, background lifecycle hooks and a stable Capacitor API. Product identity, discovery UUID policy, cryptography and payload semantics stay in the consuming app. See the transport contract.

Install

Until an npm release exists, pin an immutable Git commit over SSH:

npm install 'git+ssh://git@github.com/forgesworn/capacitor-mesh-ble.git#<commit-sha>'

For a published release:

npm install capacitor-mesh-ble@0.1.0

Sync native files

npx cap sync

API

start(...)

start(options: MeshBleStartOptions) => Promise<void>
Param Type
options MeshBleStartOptions

stop()

stop() => Promise<void>

broadcast(...)

broadcast(options: { data: string; }) => Promise<{ queuedPeers: number; }>
Param Type
options { data: string; }

Returns: Promise<{ queuedPeers: number; }>


send(...)

send(options: { peer: string; data: string; }) => Promise<{ queuedPeers: number; }>
Param Type
options { peer: string; data: string; }

Returns: Promise<{ queuedPeers: number; }>


setKeepaliveFrame(...)

setKeepaliveFrame(options: { data: string | null; }) => Promise<void>

iOS may repeat this frame while JavaScript is suspended in the background.

Param Type
options { data: string | null; }

getStatus()

getStatus() => Promise<MeshBleStatus>

Returns: Promise<MeshBleStatus>


startRssiSampling(...)

startRssiSampling(options?: MeshBleStartRssiSamplingOptions | undefined) => Promise<void>

Start periodic RSSI sampling: polls connected GATT links and attributes scanned advertisement RSSI, for peers already identified via a prior frame exchange. Off by default (battery cost). Idempotent — calling this again while sampling just updates the interval. Stops automatically when the transport is stopped.

Android only. iOS has no such method in its pluginMethods list, so a call there is REJECTED by the native bridge (not silently ignored) — callers must guard with try/catch if they run cross-platform. The web implementation, by contrast, DOES implement this method, as an inert no-op: there is no BLE hardware to sample, so it resolves rather than rejecting. NB the security-relevant honesty gate (no attribution while relaying) is likewise Android-only; if iOS ever grows sampling, the gate MUST be ported in the same change or iOS emits dishonest attributions.

Param Type
options MeshBleStartRssiSamplingOptions

stopRssiSampling()

stopRssiSampling() => Promise<void>

Stop RSSI sampling. Idempotent — safe to call when not sampling. Shares startRssiSampling's platform behaviour: Android runs the real stop, iOS rejects (bridge-level, method not implemented), web resolves as a no-op.


addListener('frame', ...)

addListener(eventName: 'frame', listenerFunc: (event: MeshBleFrameEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'frame'
listenerFunc (event: MeshBleFrameEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('peer', ...)

addListener(eventName: 'peer', listenerFunc: (event: MeshBlePeerEvent) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'peer'
listenerFunc (event: MeshBlePeerEvent) => void

Returns: Promise<PluginListenerHandle>


addListener('status', ...)

addListener(eventName: 'status', listenerFunc: (status: MeshBleStatus) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'status'
listenerFunc (status: MeshBleStatus) => void

Returns: Promise<PluginListenerHandle>


addListener('rssi', ...)

addListener(eventName: 'rssi', listenerFunc: (event: MeshBleRssiSample) => void) => Promise<PluginListenerHandle>
Param Type
eventName 'rssi'
listenerFunc (event: MeshBleRssiSample) => void

Returns: Promise<PluginListenerHandle>


removeAllListeners()

removeAllListeners() => Promise<void>

Interfaces

MeshBleStartOptions

Prop Type Description
room string Product-defined room/domain separator carried inside every envelope.
selfId string Product-defined local peer identifier, revealed only after a GATT link.
serviceUuid string Runtime discovery/GATT UUID. The plugin advertises and hosts its GATT service under exactly this one UUID. The plugin never derives or rotates it.
scanUuids string[] Optional set of service UUIDs the scanner filters for, when a product rotates its serviceUuid on a time window and must still discover peers a window or two away (clock skew, a rotation boundary crossed at slightly different moments). Match semantics are OR: a peer advertising ANY of these is a candidate. serviceUuid is always implicitly included. Omit (or leave empty) for the classic single-UUID behaviour, where the plugin scans for exactly serviceUuid. The plugin advertises only serviceUuid, never this whole set.
hops number Maximum relay hops for locally-originated frames. Zero disables relaying.
foregroundService boolean Keep the Android radio anchored in a connected-device foreground service.
notificationTitle string Android foreground-service notification title.
notificationText string Android foreground-service notification body.
maxEnvelopeBytes number Hard ceiling for a UTF-8 envelope before chunking. Default: 8192.
maxSeenIds number Deduplication window. Default: 512 envelope identifiers.
reassemblyTtlMs number Incomplete chunk assembly lifetime. Default: 30000ms.
maxClientLinks number Android client-role GATT link cap. Default: 3.
connectCooldownMs number Android reconnect backoff per BLE address. Default: 4000ms.
connectThrottleMs number Android global connection-attempt throttle. Default: 1500ms.

MeshBleStatus

Prop Type Description
native boolean
platform 'android' | 'ios' | 'web'
supported boolean
running boolean
bluetooth string
permissions string
missingPermissions string[]
room string | null
selfId string | null
serviceUuid string | null
scanUuids string[] The UUID set the scanner is filtering for (includes serviceUuid). Single entry unless the product supplied a wider scanUuids window.
advertising boolean
scanning boolean
gattServer boolean
connectedPeers number
writablePeers number
knownPeers number
queuedChunks number
txFrames number
txChunks number
rxFrames number
rxChunks number
droppedFrames number
relayedFrames number
lastError string | null
peers MeshBlePeerStatus[]
updatedAt number

MeshBlePeerStatus

Prop Type
address string
peerIds string[]
connected boolean
writable boolean
mtu number
queuedChunks number
writing boolean

MeshBleStartRssiSamplingOptions

Prop Type Description
intervalMs number Sampling interval in ms. Default: 2000, bounded 500-10000.

PluginListenerHandle

Prop Type
remove () => Promise<void>

MeshBleFrameEvent

Prop Type
from string
data string

MeshBlePeerEvent

A product peer became reachable through a learned BLE next hop, or that route was lost.

Prop Type
peer string
connected boolean

MeshBleRssiSample

A raw RSSI sample, attributed to the mesh peer id bound to the MAC it was observed on. The binding comes from a prior in-room frame exchange, and attribution is only emitted on a DIRECT, non-relaying link (discreet mode): while this device relays (crowd/mesh mode) a bound id can belong to a relayer's MAC, or be injected over the keyless crowd UUID, so no rssi event is emitted at all. It is therefore a proximity hint on a trusted-neighbour link, NOT a cryptographic identity proof — a consumer must not treat it as authentication. Banding or distance estimation is a product concern; this plugin reports dBm only. Emitted on Android only for now.

Prop Type Description
peer string The mesh peer id this sample is attributed to (bound via a prior direct frame exchange; only emitted on a non-relaying link — see the interface note).
address string The BLE MAC address the sample was observed at.
rssi number Signal strength in dBm.
source 'gatt' | 'advert' Whether the reading came from a live GATT link or a scanned advertisement.
at number Epoch ms when the sample was taken.

About

Canonical Capacitor BLE mesh transport for Forgsworn apps

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages