React Native / Expo
Run actions and realms on React Native (Hermes). All it takes is two standard polyfills the connection layer needs.
Everything in nice-code runs on React Native. The libraries use only standard web interfaces and never browser-only APIs, so the same code that runs in the browser, Node, and Cloudflare Workers runs on Hermes too. Actions and realms share one connection layer, so once it works, both do: typed actions and live realms behave the same on-device.
Hermes is missing two standard interfaces the secure connection relies on. You polyfill them once, and 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. That way global.crypto and URL already exist when the library needs
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.
Nothing else is required. 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. 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.