API Methods
Module exports
Constructor
appId and profile are required.
The native protocol is created lazily on the first start(), not in the constructor. The
constructor does subscribe to native events immediately, which is why listeners must be
registered synchronously right after it returns.
The underlying native module is process-global, so multiple
OfflineProtocol instances share
one native protocol.Lifecycle
start() initializes MLS before starting transports, so a peer can never be discovered
before key exchange is possible. It also auto-enables any internet, Nostr, or Reticulum
transport marked enabled in config.
Events
eventType is any event name or the string 'all', which receives every
event.
internet_session_superseded and mesh_stopped_by_user are held when
they arrive with no listener and delivered to the first listener that registers. Replay is
asynchronous, so a handler never fires before the on(...) call returns. Held events are
cleared by start(), by a successful enableTransport('internet', …) (for
internet_session_superseded), and by destroy().
Messaging
replyContext, mediaMetadata, and forwardInfo travel only inside the MLS-sealed rich
payload, and only to recipients whose SDK advertised support. Toward anyone else they are
silently dropped; they are never sent cleartext.sendTypingIndicator’s conversationId is opaque and echoed back on the receiving event. It
must be stable, so do not derive it from a mutable display name.
Connection Requests
connection_request_undeliverable, message_delivered,
and message_failed. The peer’s answer arrives as connection_accepted or
connection_rejected, correlated by peer address, not by message ID.
Identity
deriveAddress() needs no protocol instance, so it is safe to call before start(), which
is what makes it usable for verifying an invite or QR code. It throws if the key is not
exactly 32 bytes.
See Identity & Addressing for the full model.
Media & File Transfer
All media methods accept base64-encoded file data. The SDK handles chunking and reassembly.
sendFile() discards everything except recipient, data, and name. Use sendMedia() when you
need metadata, a caption, or reply context.MediaMetadata input type is camelCase; the media_metadata field on received events is
snake_case. See Types & Internals.
Transport Management
Bluetooth
These are degraded-path counters, not error counters. Every frame they count was still
sent, so they never surface as delivery failures. They are monotonic for the lifetime of the
protocol instance, so sample the delta and watch the trend. All three read zero when BLE is
disabled or not yet started.
Internet & Relay
checkInternetPresence is fire-and-event: the answer arrives as a
presence_updated event with source: 'internet', so
subscribe before calling. It is never throttled or deduped. force: true parks through the
reconnect window for up to ~8 seconds, which suits opening or focusing a chat. A true return
means the query reached the socket, not that an answer will come.
sendRawServerCommand is for app-owned relay features: the invite-link lifecycle
(CreateGroupInviteLink, JoinGroupViaInvite, AckGroupInviteJoin). Gate it on
isInternetReady(). Unconsumed responses arrive as internet_server_message events.
After calling internetGetNextMessage() and sending on the wire, you must call either
internetConfirmSent(messageId) or internetSendFailed(messageId).
Wi-Fi Direct (low-level)
Network & Metrics
Battery & Relay Role
These runtime methods use
'low' | 'medium' | 'high', while ProtocolConfig.relay.relayPriority
uses 'never' | 'auto' | 'always'. The SDK normalizes config values when applying them:
never → low, auto → medium, always → high.DORS
Reliability
Gradient Routing
quality is a 0.0–1.0 score. sequenceNumber is DSDV-style and defaults to 0; it is clamped
to non-negative values.
End-to-End Encryption (MLS)
MLS is initialized automatically bystart() when encryption.enabled is true (the default).
These methods provide manual control.
Session Management
establishSecureSession() imports a pending key package, creates the session, and sends the
Welcome. It throws if no key package is available. Prefer it over mlsCreateSession(), which
requires a prior mlsImportKeyPackage().
Key Packages
Low-Level Session Operations
Group Messaging
Groups use MLS for end-to-end encryption with mesh transport for delivery.meshSendGroupMessage takes priority as a string ('low', 'medium', 'high',
'critical'), unlike the 1:1 path’s numeric MessagePriority enum.Relay registration
Groups register with the relay so invite links can resolve against them.ensureGroupRegistered defaults to a 100-second timeout. The SDK re-sends an unanswered
registration every 30 seconds up to 3 attempts (~90 seconds worst case) before reporting
ack_timeout, which is what the default covers. A shorter timeout is fine for UI, since
retries continue in the background and a later group_relay_sync_changed event still fires.
Example:
User Blocking
Telemetry
pollTelemetry()
throws on a malformed native envelope, so callers can distinguish “queue empty” (null) from
bridge corruption. Re-installing a sink does not drain the queue; use
uninstallTelemetrySink() for an atomic detach and drain.
telemetryInstallId() is a persistent per-install identifier and may need declaring under
Apple’s privacy manifest or Google Play’s data safety form as a “device or other ID”. It
resolves null until the persistent scrub secret is available.Service Discovery & RPC (MeshServices)
Service Discovery turns the mesh into a decentralized service network where any device can be both consumer and provider. Devices register capabilities, others discover them through multi-hop routing, and invoke them with a request/response pattern.Setup
API
Provider Example
Consumer Example
Discovery across the mesh
Discovery queries are not limited to directly connected peers; they propagate through the mesh using the same multi-hop routing as regular messages:- A service on a device 5 hops away can still be discovered
- The
hop_countonservice_discoveredtells you how far the provider is - Multiple providers for the same service ID may respond; your app decides which to use
Android Mesh Wake
meta-data flag and
module-scope registration; see Installation.
Four caller obligations:
- Durably store received messages before
start(). The core never persists inbound content, and the receive path ACKs before emitting. - Be idempotent and cheap when there is nothing to do; the task may run in the foreground and find a live protocol.
- Re-issue what
start()does not restore: Wi-Fi Direct always, and the relay viaenableTransport('internet', …). - Resolve promptly. The budget is 60 seconds by default, after which React Native terminates the task.
Next: Events Reference
All 72 event types and their payloads.