Changelog
Every SDK release, newest first, with what changed and why. One version number covers the git tag, the npm package, the crates, the PyPI package, the Ruby gem, and the Swift, Android and Java packages. The full notes, with the reasoning attached, live in the repository.
- SDK v0.31.0
The engine runs in a browser, a Ruby binding, Flutter at parity with React Native, and sessions that heal
A browser is now a full node: the engine, MLS included, runs in a Web Worker with durable storage, links to other devices over WebRTC, finds the browsers on its own network, reaches any device over the application's Nostr relays, and links to a phone or a board over Web Bluetooth. A Ruby binding is on RubyGems, the Flutter plugin reaches the React Native module's full native surface, and live Bluetooth LE signal strength arrives as a peer_rssi event. Two devices that share only a Nostr relay now form a session, and a pair split into two sessions heals itself.
- Browser (experimental): offline-protocol-web on crates.io compiles the full engine for WebAssembly. A node keeps its identity and groups in sealed origin-private files, links tabs over a BroadcastChannel and devices over WebRTC set up from two codes, meets by room code through Nostr relays, and finds the browsers behind one network address in a nearby room. Every browser page is tested in Chromium, Firefox and WebKit. The JavaScript package is built from the SDK repository.
- Browser to device: in Chromium, BluetoothCarrier links a page to a phone or a board running the SDK as a peripheral. A browser can also remove a group member, read a group's members and epoch, and send ephemeral messages that go once over a live link for state the next send replaces, such as a game's positions.
- Ruby: the offline_protocol gem on RubyGems, generated from the interface definition by UniFFI's Ruby generator and calling the native library through the ffi gem, for Ruby 3.3 or later. CI runs a consumer check against the library on Linux, macOS and Windows, and examples/ruby-app pairs two devices, sends a file and talks in an MLS group.
- Flutter: MeshSdk exposes the React Native module's native surface to Dart through a generated engine channel, Nostr and Reticulum configurations now start, and on Android start() runs the same mesh foreground service as React Native. A new CI job compiles the iOS plugin with the shared bridge sources.
- Live Bluetooth LE signal strength: a peer_rssi event reports a linked peer's RSSI from every scan, at most four a second per peer, only while the app listens. While it flows, the mesh relay ranks neighbours by the smoothed readings.
- Sessions: a send to a device known only by its address asks the relays for its key packages on iOS, Android and Flutter as in the browser. A pair split into two MLS sessions is detected with an exporter tag and heals, a lost re-key no longer leaves one side without a session, and an old group frame that can never decrypt is acknowledged instead of filling the send window.
- Bluetooth LE and links: the iOS peripheral serves the Hello characteristic, so an iPhone names a central it cannot dial back, such as a browser. A browser relinks after the other device restarts, and a room hosted through Nostr stays reachable across sleep and network changes. Nostr signing moves to k256 0.14 for RUSTSEC-2026-0387.
- Upgrading: nothing is removed from any binding. Rust code that matches every Event or builds SendMessageOptions, GroupSendOptions or KeyPackagePayload in full needs the new peer_rssi, ephemeral and no_session fields, and an exhaustive TypeScript switch over events needs a peer_rssi case. Session healing needs both devices on 0.31.0.
- SDK v0.30.0
Smaller libraries, C, Java and Flutter bindings, and a telemetry security fix
Link-time optimisation now reaches every library a release ships, and the iOS and Android libraries are built for size: the static library the iOS framework is built from falls from 138 MB to 23 MB, and the npm package from 466.4 MB to 100.7 MB unpacked. Three new bindings reach the SDK from C, Java and Flutter. The telemetry uploader now trusts only the roots the ingest's certificate issuers use, so apps that call enableTelemetry on 0.26.0 to 0.29.0 should update. A two-phone Bluetooth LE volume test led to a set of fixes for links offered more than they carry.
- Security: the telemetry uploader in 0.26.0 to 0.29.0 trusted every root in the bundled Mozilla set, including an eMudhra root withdrawn after the October 2026 key compromise. It now trusts sixteen roots from the four authorities the ingest uses, pinned in a test. Nothing else changes for an app.
- Sizes, measured on the macOS arm64 build: the static library behind the iOS framework 138 MB to 23 MB; the shared library built for size 10.9 MB to 8.5 MB, at about 40% slower MLS sealing and opening on the benches and at most a third longer on other paths. Desktop libraries and Python wheels stay built for speed.
- C: offline_protocol.h, about a dozen functions over the native library, calling any SDK method by name with JSON. The header ships in every desktop archive; the iOS XCFramework carries the same functions.
- Java: com.offlineprotocol:offline-protocol-java on Maven Central, for JDK 22 or later through the foreign function API, with the native library taken from the release's desktop archives. Flutter: a preview plugin for iOS and Android, added as a path dependency from the repository until it is on pub.dev.
- React Native: sendFileFromPath sends a file by path so it never crosses the bridge as base64, and signDataBytes and verifySignatureBytes sign large payloads without boxing every byte. send_file_from_path reaches every binding.
- Reliability: at most 16 messages in flight to one nearby peer, acknowledgements ahead of data on Bluetooth LE, a full acknowledgement table no longer stops the engine, sendMessage no longer reports a failure for a message it sent, a receiver far behind its sender re-keys, and large Bluetooth LE messages are no longer torn under load. React Native replays events lost on the way to JavaScript.
- Upgrading: building the native library from source needs Rust 1.91, and the iOS bridge Swift 5.10 (Xcode 15.3). Telemetry now counts only the app's own messages, so dashboard delivered counts read lower, and a full outbox refuses the newest message instead of failing the oldest.
- SDK v0.29.0
A box runs the service on every carrier, and programs call each other over HTTP
The Python service now starts every transport its configuration enables: peer streams found over the LAN, Bluetooth LE in both roles through BlueZ, and the internet relay. An HTTP front lets a program that does not use the SDK call a service on another device with one plain HTTP request. Service request and response bodies are sealed end to end, and two devices that have never met start a session through a third. Android now meets iPhones and Python hosts on a shared Wi-Fi network.
- pip install "offline-protocol-sdk[lan,http]" adds the HTTP front and offline-protocol-verify, a command that drives the service and reports what it proved. A Dockerfile in the repository builds the service image from PyPI; no image is published to a registry.
- One PUT registers a service with the front; a request to <service>.<device>.offline.protocol.internal reaches it on another device inside an end-to-end encrypted message, and the provider sees the caller's authenticated address in X-Offline-Protocol-Sender. The front does not authorize; the provider decides who it serves.
- Service bodies sent with sendServiceRequest are sealed inside the MLS session once the recipient runs 0.29 and the session is confirmed. The first request to a provider never met, and discovery, still travel signed and readable.
- A key package and its Welcome now cross the mesh, so a message to a device reachable only through another is delivered instead of waiting out its lifetime. A message the mesh carried now settles with message_delivered.
- Android advertises and browses DNS-SD on its Wi-Fi network when the peer stream is on, with no Wi-Fi Direct group. Android Bluetooth LE closes dials that never connect and gives back the client slot of a dropped link.
- In our test on two Raspberry Pi 4s joined only through a laptop, an HTTP call through the middle box answered in 5.2 s, and an HTTP call between the two Pis over Bluetooth LE alone passed. Reported results, not guarantees.
- Breaking, narrowly: the two service events gain encrypted, so Rust code that names every field, or a TypeScript literal of either event, needs it. Apps that target Android 17 must request ACCESS_LOCAL_NETWORK for the LAN carrier.
- SDK v0.28.0
The SDK ships outside React Native, and the phone peer stream carries traffic
A release now publishes Python wheels to PyPI, a Swift package, and an Android library, the last two built from the same bridge sources as the React Native module. The Swift and Android packages are early: their public API is not settled yet, and the Android library is a download from the GitHub release, not Maven Central. On phones the peer stream now carries traffic: Android can form its own Wi-Fi Direct group with no system dialog, and iOS runs the slot on Network framework instead of MultipeerConnectivity.
- pip install offline-protocol-sdk, for Python 3.10 to 3.13, with wheels for macOS arm64, Linux x86_64 and aarch64 (glibc 2.34+), and Windows x86_64.
- A Python host can run one engine for several local applications through the local API, JSON-RPC 2.0 over a WebSocket. Built-in file stores, sealed under a store key, serve hosts with no platform keystore.
- Custody, off by default: a device can hold a neighbour's replication frames for hours (6 by default) and hand them on when the recipient appears.
- The leaf builds in CI for the ESP32 RISC-V parts (C6, H2, C3, C2), Cortex-M0, and WASI, beside Cortex-M33.
- A send can name the application it is for, and received events carry that app id. Bluetooth LE peers find each other in crowded rooms, and links recover after an iPhone's Bluetooth power-cycle and, on Android, after a toggle or a stack crash.
- Breaking: an iPhone on 0.28 does not see one on 0.27 or earlier over the peer stream; Python InternetManager requires app_id; SecurityWarningCode gains KEY_PACKAGE_OUTSIDE_VALIDITY_WINDOW. Deduplication now keeps 5,000 ids for 7 days.
- SDK v0.27.0
Replicated documents can be removed and narrowed, and a Python host joins the mesh
Documents can now be removed from every replica, a space can replicate only the documents an app is interested in, and attachment bytes move inside a group. The peer-stream transport is registered on every binding, so a Python host can reach another over a LAN or a routed mesh, and relay group traffic reaches phones again.
- DataStore.setInterest narrows a space to matching documents; DataStore.fetchAttachmentFrom pulls attachment bytes from a peer.
- The peer-stream transport is registered on iOS, Android, Python, and Rust, with a Python Bluetooth LE peripheral a phone can verify.
- Relay group notifications reach iOS and Android members again; a broadcast through the relay reaches a Python member.
- A message evicted from a full outbox stays failed and is settled once; a send every carrier refused as unreachable says so.
- WIFI_DIRECT_MAX_PAYLOAD_SIZE is removed from the transport crate; the telemetry pipe caps message.failed rows per reason.
- SDK v0.26.0
The SDK ships its own telemetry, and delivery state survives a restart
Telemetry moves into the core. Enable it with a key from the developer portal and the SDK collects, folds, queues, and uploads accepted events on its own background thread, to one endpoint fixed at build time, over TLS with a bundled root set. The sink API is gone. Frames waiting on a slow handshake and the set of message ids already seen are now persisted and sealed like the outbox.
- enableTelemetry, disableTelemetry, flushTelemetry, telemetryStats, and endTelemetrySession on every binding; installTelemetrySink and onTelemetry are removed.
- The pending-decryption queue and the deduplicator's seen set are written to protocol-state storage and restored on launch.
- pendingQueue.pendingTtlMs defaults to 24 hours, was 30 minutes. reliability.dedup defaults to 2,000 ids over 24 hours, was 1,000 over 1 hour.
- A new offline-protocol-telemetry-wire crate pins the batch envelope byte for byte; twelve crates on crates.io.
- ADR 0024 records why the SDK ships its own TLS stack for telemetry. The threat model gains a network-egress section.
- SDK v0.25.0
Reticulum speaks the gateway contract, and the wire spec can be implemented from the text
Both mobile managers now complete the gateway-contract attach handshake, sends settle on the gateway's verdict rather than on the socket write, and an unreachable recipient parks the message and offers it to the mesh. Before this release a device on Reticulum could not receive anything.
- Reticulum attach: Identify, Challenge, DeclareAddress, AddressDeclared, Capabilities, then the carrier announced; two new security warning codes for a refused or mismatched declaration.
- RetryConfig.edgeDrivenUnreachableDm, opt-in, bounds probing of a durably unreachable direct message to reachability edges.
- Python internet send loop is activity-adaptive: a warm round trip drops from about 100 ms to single-digit milliseconds.
- iOS state restoration no longer chases peripherals whose owners are gone; BLE re-discovery no longer repeats subscription and handshake work.
- Wire specification gains a normative primitive-encoding table and seven conformance vector files generated by an independent second implementation.
- SDK v0.24.1
iOS React Native bridge corrected
Eight methods that were unreachable from the iOS bridge since 0.21 and 0.22, including wipePersistedState and the relay configuration, now reach the core; seven methods that read the wrong argument slots are fixed, so every iOS message no longer defaults to medium priority and presence no longer defaults to online.
- Fifteen trapping conversions in the iOS bridge replaced with checked ones.
- No wire, configuration, or Android change.
- SDK v0.24.0
A constrained device as a real peer
The leaf profile ships: a no_std Rust crate that lets a lock, a sensor, or a relay speak the protocol with RFC 9420 MLS as a never-committing member. The shared envelope codec, address derivation, and signing payloads move into a bare-metal crate both ends link.
- offline-protocol-leaf and offline-protocol-sealed crates; offline-protocol-core compiles without std.
- Control frames are signed under offline-ctrl-v2 with the timestamp inside the signature, a freshness window, and a downgrade ratchet.
- Key package lifetimes are capped at 90 days on acceptance; rekeySession gives applications post-compromise security on demand.
- npm releases published with trusted publishing and provenance.
- SDK v0.23.0
Edge sync: replicated documents
Offline-first shared state: map, list, text, and counter documents that any member of a space edits while disconnected and that merge deterministically when replicas meet, exchanged as compact deltas inside MLS-scoped spaces, on a swappable storage adapter.
- DataStore on React Native, iOS, Android, Python, and Rust, enabled by default.
- Attachments by reference with pairwise fetch and hash verification; group replication.
- Gateway contract v1 specified: attach, submit, verdict, presence, capabilities.
- SDK v0.22.0
Battery-aware relaying and an observed relay role
Relay standing is now observed from traffic actually forwarded rather than predicted from thresholds, forwarding effort scales with charge, and applications can feed battery state to the core.
- setBatteryState, RelayPriority never/auto/always, and tunable mesh relay budgets with stats.
- Username discovery and invite blobs, both default-off.
- Documentation restructured into a protocol specification, threat model, state machines, and architecture decision records.
- SDK v0.21.0
Self-certifying addresses and multi-hop forwarding
Identity becomes an address derived from the device's Ed25519 key, so a peer proves its address by presenting the key and there is nothing to pin or look up. Unsigned control frames are refused, and messages relay across the mesh hop by hop under a forwarding governor.
- ProtocolConfig.userId is replaced by profile; recipients are off1 addresses.
- Trust-on-first-use pinning removed in favour of address-from-key verification.
- Crates published to crates.io in lockstep with the npm package.
- Baseline
Current production platform
The capabilities live in the SDK today, on a network that has reached 350,000+ devices. This entry establishes the release log; subsequent changes are recorded above it as they ship.
- DORS multi-transport routing with automatic failover, multi-hop relay, retry, and deduplication.
- OfflineID: Ed25519 device-owned identity, verifiable device-to-device with zero connectivity.
- Service discovery: advertise, discover, and invoke capabilities across the mesh with no server or DNS.
- MLS (RFC 9420) end-to-end encryption on every session by default.
- Chunked, resumable file transfer up to 100 MB between authenticated peers.
- Opt-in telemetry: one in-process stream of protocol events, transport transitions, routing decisions, and MLS lifecycle, emitted only after your app installs a sink, with identifiers scrubbed by default.
- One SDK surface: a TypeScript API over a shared Rust core, identical on iOS and Android.

