Runtime support matrix
Which runtimes the SDK runs in, which store adapter each one uses, and the exact documented position on Expo Go.
Applies to @open-e2ee/signal-protocol-sdk 6.0.x. Public APIs and persisted formats follow semantic versioning.
The protocol code is Pure TypeScript. Each runtime supplies secure randomness and a compatible storage adapter.
The matrix
| Runtime | Status | Store adapter | Notes |
|---|---|---|---|
| Expo, development or release build | Supported — primary target | /local/store/expo — expoStore() | Expo SQLite with SQLCipher; requires application-owned database bootstrap |
| Expo Go | Not available for the Expo store | — | See below |
| React Native, bare | Supported | /local/store/react-native — await reactNativeStore({ storage }) | Full store contract; you supply the key-value backend and verify it with the exported backend-conformance kit |
| Browser | Supported | /local/store/web — await indexedDbStore() | Full store contract; deployment requires the browser threat-model review |
Node >= 22.12 | Supported for non-mobile use | /local/store/node — await nodeStore({ dataDir }) | Filesystem-backed; single writer; full store contract |
| Any, tests and local development | Development only | /local/store/memory — inMemoryStore() | In-memory; never ship it |
The package is ESM-only and declares engines.node >= 22.12.0. Every peer dependency is optional. Install the ones your chosen adapter needs. Mapping in Package subpaths.
Run the examples
Open the playground to send an encrypted message and decrypt a reply. The page and developer console show actual results. You need no account or installation.
| Example | Run and inspect | Storage |
|---|---|---|
| Browser | Edit on StackBlitz or run the source locally | Both identities and the relay use temporary memory. |
| Expo / Hermes | Run the native example on iOS or Android. | Alice keeps her identity and sessions in SQLCipher. Bob and the relay use memory. |
The Expo example checks Alice’s identity and another exchange after restart. SQLCipher requires a native development or release build. Expo Go does not include it.
Expo Go
The one statement the SDK repository makes about Expo Go, verbatim: "SQLCipher requires a development build and is not available in Expo Go."
It appears in the Expo store's own README, in the section describing the application-owned encrypted-database bootstrap. That is the whole documented position. This page does not extend it.
Read literally, the constraint is on SQLCipher, which the Expo store adapter requires. The practical consequence for an application using expoStore() is that it needs an Expo development build, npx expo run:ios, npx expo run:android, or an EAS build, rather than the Expo Go client.
The repository does not claim that another adapter combination supports Expo Go. It also does not claim that a future release will change support. Test your build if you need a definitive result. A database bootstrap failure appears during client creation, not during a session.
Per-runtime guidance
Expo
Use the Expo-specific storage adapter instead of browser shims. Call configureSignalProtocolExpoDbBindings() with your Drizzle and raw database handles before expoStore(). Your application owns the database bootstrap by design. It must open SQLCipher, apply the key before schema access, and create or migrate exported tables. The related key management requires a product decision.
Treat backup, device transfer, and secure-storage availability as explicit product policy. Your app decides whether iOS or Android device backups include the encrypted store. It also decides what happens during device transfer. The SDK does not make these choices. See recovery, backup, and migration.
Verify behavior on both iOS and Android release builds. Debug builds differ in keychain behavior, background execution, and native module availability, and a store that works in development can fail on a signed release.
Bind the relay subscription to the app state with useRelayLifecycle. See binding the relay to the app state.
Start at Expo setup.
Browser
Treat IndexedDB and local storage as application-controlled persistence boundaries. They are not a secure enclave. The web adapter's own README is unambiguous about the ceiling: encryption at rest is therefore not an XSS defense. Script executing on your origin has whatever the page has, encrypted database or not.
Consider cross-tab coordination and origin isolation. Two tabs of the same origin are two writers against one store. The ratchet does not tolerate concurrent divergent writes. Decide explicitly whether you run one client per origin with a leader tab, or accept the constraint of a single active tab.
The IndexedDB adapter is supported. It implements the core store contract, Sesame records, sender-key state, retry records, and recovery helpers, and its graduation gates — contract suites in real Chromium, Firefox, and WebKit, multi-tab, interruption, storage-pressure, and soak — run on every change to the source repository.
Keep the relay subscription running while a tab is hidden. A hidden tab keeps its socket open, and useRelayLifecycle does nothing on web.
Start at browser setup.
Node
NodeSignalProtocolStore implements the complete SignalProtocolLocalStore contract. This includes identity keys, prekeys, sessions, trust decisions, Sesame devices, sender keys, and message records. The class declares implements SignalProtocolLocalStore. A missing or changed member therefore fails the SDK build instead of appearing at runtime. Multi-device linking and both group APIs are available on this adapter.
Keep storage paths and file permissions explicit. nodeStore({ dataDir }) writes protocol state to a directory you name. The confidentiality of that directory is the confidentiality of the identity keys.
Do not place the store on an eventually consistent or multi-writer network filesystem. The Node adapter's README states this requirement directly. Two processes against one store directory over a network mount is a corruption path.
Do not assume process memory can be reliably scrubbed. JavaScript engines copy, intern, and garbage-collect without giving the program control. The SDK does what it can with secureZeroBytes, and that is a mitigation, not an erasure guarantee.
Test the bundled artifact under every maintained Node line you support. ESM resolution, engines enforcement, and native-free crypto all behave slightly differently across major versions, and the thing you ship is the bundle, not the source tree.
Start at Node setup.
React Native, bare
The bare adapter takes a key-value backend you provide: await reactNativeStore({ storage }), where storage implements ReactNativeKeyValueStorage. Its atomicWrite is a security boundary, not a batching optimization: an implementation that loses atomicity there loses the trust-commit guarantee described in Adapter interfaces.
The subpath exports assertBackendConformance, a backend-conformance kit that proves a backend upholds the contract — atomicity, prefix isolation, unicode round-trips, and, with the reopen hook, durability across restart. Run it against your backend from your own tests before you ship. The SDK runs the same kit against its reference backend on the Hermes engine on every change.
Bind the relay subscription to the app state with useRelayLifecycle or bindRelayLifecycle(signal, AppState), as on Expo.
Why the implementation is pure TypeScript
The SDK uses @noble/curves for key agreement. AES-CBC and AES-GCM use Web Crypto when crypto.subtle exists. Hermes uses the SDK’s Noble AES implementation when that API is absent.
The native random provider uses crypto.getRandomValues when available. Otherwise, it gets secure bytes from expo-crypto. Native Expo modules also provide SQLCipher storage and the database key vault.
The Expo example includes these dependencies and their native configuration. The browser example uses browser cryptography APIs and temporary memory storage.
JavaScript engines do not guarantee machine-level constant-time behavior or reliable memory zeroization. The SDK has no FIPS 140 validation. See security and protocol policy.
Next
- Expo, browser, Node: runtime setup
- Choosing adapters: picking a store for your runtime
- Package subpaths: the peer dependency map
- Adapter interfaces: what a store must guarantee