Skip to main content

API Methods

Module exports

All types, enums, and constants are re-exported from the package root.

Constructor

There is no singleton and no factory. See Configuration for all options. Only 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.
wipePersistedState() is irreversible. Call it after destroy(), on logout or account switch. It rotates the MLS and Nostr identities, so peers see a desync and must re-establish. The native side rejects it if the named account is the one currently running. It is safe to call twice. Apps with custom storage providers must erase their own containers separately.

Events

eventType is any event name or the string 'all', which receives every event.
Register listeners synchronously after construction. The SDK subscribes to native events in its own constructor, so events can arrive before your handlers attach, and apart from two one-shot types, unlistened events are dropped by design.Delivery is at-least-once: handlers must be idempotent.
One-shot replay. 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

senderName and initialMessage travel in plaintext, because connection requests necessarily precede the MLS session.
The returned message ID correlates 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.
The 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.
A superseded session never reconnects on its own. Recovery is a deliberate enableTransport('internet', { serverAddress }).You normally do not need forceInternetReconnect() on foreground: both native bridges auto-heal after a background stay. Calling it on every foreground double-reconnects and forces a wasted group re-registration. Keep it for a user-initiated “reconnect now”.
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.
Do not send SDK-managed frame types through sendRawServerCommand: SendMessage, CreateGroup, member deltas, LeaveGroup, CheckPresence. A raw CreateGroup or LeaveGroup desyncs the SDK’s registration tracking. It returns false when not connected and authenticated, when the JSON is invalid, or when the client-side limiter deferred (28 burst / 9 per second).
After calling internetGetNextMessage() and sending on the wire, you must call either internetConfirmSent(messageId) or internetSendFailed(messageId).

Wi-Fi Direct (low-level)

The three deprecated methods must not be called by application code. The bundled Wi-Fi Direct managers no longer call them either.senderId is treated by the core as the peer’s proven identity: it becomes the frame’s transport peer identity and is matched against the message sender. Wi-Fi Direct has no handshake that yields such a value, so an unproven one is either rejected or accepted into routing state under a name anyone could claim. wifiDirectPeerConnected additionally admits an unproven peer into the capacity-bounded neighbor table, evicting genuine neighbors. WifiDirectTransport is not registered, so frames passed here are dropped anyway.

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

These three updaters became fallible in v0.17.0. Zero is rejected for maxTrackedMessages and retentionTimeSecs.

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 by start() 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

Pass the listener to installTelemetrySink rather than registering it afterwards with onTelemetry. Registering after the install resolves leaves a window where push records are dropped.
The poll queue is bounded at 1024 records and drops the oldest on overflow. 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_count on service_discovered tells you how far the provider is
  • Multiple providers for the same service ID may respond; your app decides which to use

Android Mesh Wake

Registers the Headless JS task that restores the mesh after Android kills the process. Android only; a no-op on iOS. Requires both the manifest meta-data flag and module-scope registration; see Installation. Four caller obligations:
  1. Durably store received messages before start(). The core never persists inbound content, and the receive path ACKs before emitting.
  2. Be idempotent and cheap when there is nothing to do; the task may run in the foreground and find a live protocol.
  3. Re-issue what start() does not restore: Wi-Fi Direct always, and the relay via enableTransport('internet', …).
  4. Resolve promptly. The budget is 60 seconds by default, after which React Native terminates the task.
If the task never registers, throws, or declines, the keep-alive stops itself on a watchdog.

Next: Events Reference

All 72 event types and their payloads.