React Native / Expo
Run the whole stack on React Native (Hermes) — actions and realms both — with two standard polyfills the connection layer needs.
Everything in nice-code runs on React Native. The libraries use only standard web interfaces — never browser-only APIs — so the same code that runs in the browser, Node, and Cloudflare Workers runs on Hermes too. Because the connection layer is shared, unblocking it unblocks both planes: typed actions and live realms work identically on-device.
Hermes is missing two standard interfaces the secure connection relies on. You polyfill them once — there’s nothing nice-code-specific to install.
What’s missing on Hermes
Section titled “What’s missing on Hermes”- WebCrypto (
crypto.subtle) — the handshake and encryption need Ed25519 / X25519 / HKDF / AES-GCM. URL— Hermes’ built-inURLis incomplete; carriers use it to build WebSocket / HTTP urls.
Install the two polyfill packages:
bun add react-native-quick-crypto react-native-url-polyfillRun them as the very first side-effect of your app entry — before any connection, ActionRuntime,
or realm is constructed (so global.crypto and URL already exist by the time the library touches
them):
// `URL` is incomplete in Hermes — fix it first.import "react-native-url-polyfill/auto";
import { install } from "react-native-quick-crypto";
// Patches `global.crypto` with OpenSSL-backed WebCrypto (SubtleCrypto).install();// Import the polyfills before anything else (e.g. before "expo-router/entry").import "./polyfills";import "expo-router/entry";react-native-quick-crypto compiles OpenSSL through Nitro / JSI, so it needs the new architecture
enabled and a recent deployment target (iOS 16.4+, Android minSdkVersion 24+). Configure that via
expo-build-properties if you’re on Expo.
That’s the whole requirement. The crypto identity, secure WebSocket, HTTP exchange, and realms all work from there exactly as they do on every other platform.
Storage on React Native
Section titled “Storage on React Native”The crypto identity is persisted through a StorageAdapter — and on a secure connection this store
must be durable, or the pinned identity is rejected after the first launch (see
Identity & Trust). On the
web that’s localStorage; on React Native, back it with
@react-native-async-storage/async-storage:
import { EStorageAdapterType, type IStorageAdapterMethods_String } from "@nice-code/util";import AsyncStorage from "@react-native-async-storage/async-storage";
export const asyncStorageMethods: IStorageAdapterMethods_String = { type: EStorageAdapterType.string, setItem: (key, value) => AsyncStorage.setItem(key, value), getItem: (key) => AsyncStorage.getItem(key), removeItem: (key) => AsyncStorage.removeItem(key),};Wrap it in a StorageAdapter and pass it as the storage on createWireClient (realm-only) or
connectChannel (with actions) — the same way the web demo passes a localStorage-backed adapter.