React Native implementation of the peerkit transport layer. Wires
@peerkit/transport-libp2p-core to a libp2p stack configured for mobile:
WebSockets (outbound), WebRTC, and circuit-relay-v2 client.
npm install @peerkit/transport-libp2p-react-native
npm install react-native-get-random-values react-native-quick-crypto react-native-webrtc
The three react-native-* peer dependencies provide native modules and must
be linked via the standard React Native autolinking flow (iOS: pod install,
Android: Gradle sync).
libp2p depends on Node built-ins (crypto, stream, buffer, events,
process) and on the Web Crypto / WebRTC globals. Hermes provides none of
those out of the box (Hermes is Meta's JavaScript engine for React
Native). The package ships a single side-effect entry that
installs everything in the right order.
Add as the very first import of the app entry (typically index.js or
App.tsx):
import "@peerkit/transport-libp2p-react-native/polyfills";
The polyfill module:
globalThis.crypto.getRandomValues via
react-native-get-random-values. Must run first — peer-id generation and
noise key generation reach for RNG during module init.react-native-quick-crypto, providing a JSI-backed
globalThis.crypto.subtle.react-native-webrtc globals (RTCPeerConnection,
RTCSessionDescription, etc.) consumed by @libp2p/webrtc.globalThis.Buffer and globalThis.process from the buffer and
process shims so libp2p code that touches these as globals (rather than
ES module imports) works.Event / EventTarget / CustomEvent globals that
Hermes lacks but @libp2p/interface extends at module init.TextEncoder / TextDecoder globals that Hermes lacks but
uint8arrays constructs at module init.Metro (React Native's JavaScript bundler) must rewrite Node-style bare
imports to their React Native shims. Add
the following to metro.config.js:
const { getDefaultConfig } = require("@react-native/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.extraNodeModules = {
...config.resolver.extraNodeModules,
crypto: require.resolve("react-native-quick-crypto"),
stream: require.resolve("readable-stream"),
buffer: require.resolve("buffer"),
events: require.resolve("events"),
process: require.resolve("process"),
};
// libp2p 3.x publishes a `browser` conditional export that is broken under
// Metro's package-exports resolver — see <https://github.com/libp2p/js-libp2p/issues/2969>.
// Enabling the resolver is required for several libp2p subpackages to
// resolve at all; per-package overrides may be needed if a specific module
// fails to bundle.
config.resolver.unstable_enablePackageExports = true;
module.exports = config;
Compile public class fields with spec [[Define]] semantics by setting the
setPublicClassFields: false assumption:
module.exports = {
presets: ["babel-preset-expo"], // or "module:@react-native/babel-preset"
assumptions: {
setPublicClassFields: false,
},
};
This is required, not optional. babel-preset-expo defaults to loose
([[Set]]) class fields, which compile an uninitialized field declaration to
this.field = void 0. React Native's webapis define read-only prototype
constants — notably Event.prototype.NONE (and CAPTURING_PHASE /
AT_TARGET / BUBBLING_PHASE) via Object.defineProperty without
writable: true. The matching this.NONE = void 0 walks the prototype chain
and throws Cannot assign to read-only property 'NONE' the moment libp2p or
WebRTC construct an Event. The [[Define]] assumption drops the bare
declaration, so the read-only constant is never assigned.
Do not add ["@babel/plugin-transform-private-methods", { loose: true }]:
its loose option sets the opposite assumption (setPublicClassFields: true)
and reintroduces the crash. libp2p's private fields (#field) are transformed
by the preset without it.
import "@peerkit/transport-libp2p-react-native/polyfills";
import { createNode } from "@peerkit/transport-libp2p-react-native";
const transport = await createNode({
networkAccessBytes: new Uint8Array([
/* network access bytes */
]),
bootstrapRelays: ["/dns4/relay.example.com/tcp/443/wss/p2p/12D3Koo..."],
// Optional: defaults to ["/p2p-circuit", "/webrtc"].
addrs: ["/p2p-circuit", "/webrtc"],
});
createNode returns a TransportLibp2p instance from
@peerkit/transport-libp2p-core. Refer to that package's documentation for
the public API (onAccessConnect, onAgentsConnect, onMessageConnect,
sendAgents, sendMessage, etc.).
createNode wires Noise with a JSI-backed ICryptoInterface over
react-native-quick-crypto. SHA-256, HKDF and ChaCha20-Poly1305 run as
native code; X25519 stays on the pure-JS Noise default because
ICryptoInterface is synchronous and quick-crypto's X25519 surface is not.
The adapter is also exported standalone for consumers who want to wire it into their own libp2p stack:
import { quickCryptoNoise } from "@peerkit/transport-libp2p-react-native/quick-crypto-noise";
Importing the package's main entry pulls react-native-quick-crypto at
module load time. The package is therefore intended exclusively for React
Native bundles — do not import it from Node test code. Type-only imports
(import type { ... }) remain safe in any TypeScript context.