react-native-wifi-p2p-sync
v0.1.3
Published
Android WiFi Direct P2P discovery and data sync for React Native
Downloads
532
Maintainers
Readme
react-native-wifi-p2p-sync
Android WiFi Direct (P2P) discovery and data sync for React Native.
Devices running the same app automatically find each other over WiFi Direct, exchange data on connect, and relay live item updates via gossip. No internet or server required.
Android only. Requires a native build — run
npx react-native run-androidorexpo run:android.
Installation
npm install react-native-wifi-p2p-syncThe library is fully self-contained with zero runtime dependencies. WiFi Direct management, DNS-SD discovery, TCP transport, and connection lifecycle are all handled by the library's own native module using standard Android and Java APIs.
Rebuild your Android app after installing:
npx react-native run-android
# or
expo run:androidPermissions
The library's AndroidManifest.xml declares the required permissions. Android's manifest merger will include them automatically, but you still need to request runtime permissions before calling start().
For reference, these are the permissions declared:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- API 33+ -->
<uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />Expo — if you need to add extra permissions to app.json:
{
"expo": {
"android": {
"permissions": [
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.NEARBY_WIFI_DEVICES",
"android.permission.CHANGE_WIFI_STATE",
"android.permission.ACCESS_WIFI_STATE",
"android.permission.INTERNET"
]
}
}
}Bare React Native — permissions are merged automatically. If you need to add them manually, add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />Quick start
Wrap your app (or the relevant subtree) in <P2pSyncProvider>:
import { P2pSyncProvider } from 'react-native-wifi-p2p-sync';
export default function RootLayout() {
return (
<P2pSyncProvider
serviceId="com.myapp.sync" // unique per-app scope
getData={() => db.getAllItems()} // called when a peer connects
onReceiveData={async (data) => {
// JSON data from a peer on first connect
await db.upsertMany(data as Item[]);
}}
onReceiveItem={(item) => {
// single live update broadcast by a peer
db.upsert(item);
}}
>
<Stack />
</P2pSyncProvider>
);
}API
<P2pSyncProvider>
Starts WiFi Direct discovery and manages the connection lifecycle.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| serviceId | string | — | DNS-SD service scope. Use a reverse-domain string unique to your app. |
| getData | () => unknown \| Promise<unknown> | — | Returns local data (any JSON-serialisable value) to send when a peer connects. |
| onReceiveData | (data: unknown) => void \| Promise<void> | — | Handles JSON data received from a peer. |
| onReceiveItem? | (item: unknown) => void \| Promise<void> | — | Handles a single live item update. Falls back to onReceiveData(item) if omitted. |
| onPeerConnected? | (peerId: string) => void | — | Called after handshake completes. |
| onPeerDisconnected? | (peerId: string) => void | — | Called when a peer disconnects. |
| gossipTtl? | number | 5 | Initial hop count for gossip broadcasts. |
| seenSetExpiryMs? | number | 60000 | How long (ms) a seen-item entry is kept to suppress duplicate gossip. |
| handshakeTimeoutMs? | number | 10000 | How long (ms) to wait for a peer handshake before disconnecting. |
Hooks
useP2pSync()
Primary hook — covers the common use case.
const { peers, discovered, status, isRunning, isSyncing, lastSyncAt, error, broadcast, restart } = useP2pSync();useNearbyState()
Full internal state — useful for a debug or nearby-devices screen.
const {
peers, // Peer[] — fully connected & handshaked
discoveredDevices,// DiscoveredDevice[] — found but not yet connected
incomingPeers, // string[] — connecting, awaiting handshake
role, // 'undecided' | 'hub' | 'spoke'
status, // SyncStatus
error, // string | null
isSyncing,
lastSyncAt,
rawDeviceCount,
myDeviceName,
myDeviceAddress,
restart,
} = useNearbyState();Focused hooks
import {
usePeers, // → Peer[]
useDiscoveredDevices, // → DiscoveredDevice[]
useSyncStatus, // → SyncStatus
useIsSyncing, // → boolean
useLastSyncAt, // → number | null
useSyncError, // → string | null
} from 'react-native-wifi-p2p-sync';broadcast(item)
Push a single item update to all connected peers. Relayed via gossip up to gossipTtl hops (default 5). Deduplication is handled automatically for items that have id and updatedAt fields.
const { broadcast } = useP2pSync();
// after saving locally:
broadcast({ id: 'abc', value: 42, updatedAt: Date.now() });diffIncomingChanges(local, remote, options)
LWW (last-write-wins) merge helper. Returns only the remote items that are newer than or absent from local storage.
import { diffIncomingChanges } from 'react-native-wifi-p2p-sync';
onReceiveData: async (data) => {
const local = await db.getAllItems();
const toUpsert = diffIncomingChanges(local, data as Item[], {
key: 'id',
clock: 'updatedAt',
});
await db.upsertMany(toUpsert);
}Types
type SyncStatus = 'idle' | 'starting' | 'active' | 'syncing' | 'error';
type NearbyRole = 'undecided' | 'hub' | 'spoke';
interface Peer {
peerId: string;
name: string;
status: 'found' | 'connecting' | 'connected' | 'disconnected';
}
interface DiscoveredDevice {
address: string;
name: string;
status: 'queued' | 'connecting';
}How it works
- Discovery — Each device registers a DNS-SD service (
_rnwifip2psync._tcp) embeddingserviceId,deviceId, anddeviceNamein TXT records. Only peers with a matchingserviceIdare considered. - Connection — The first device to discover a peer initiates a WiFi Direct connection. One device becomes the Group Owner (hub), the other a spoke. The library's native module registers a
BroadcastReceiverto track group formation events. A TCP socket is opened over the group network. - Handshake — Both sides exchange a
HANDSHAKEmessage containingserviceIdfor app-level scoping. Mismatched apps are disconnected immediately. - Sync — After handshake, both sides call
getData()and send aSYNC_RESPONSEto the other. Each side callsonReceiveDatawith the peer's data and replies withACK. When the ACK is received the session closes and both devices rejoin discovery. - Live updates —
broadcast()sends anITEM_UPDATEto all connected peers, which relay it with a decrementing TTL (gossip).
Compatibility
| React Native | Android API | Status | |---|---|---| | 0.73 – 0.82 | 24+ (Android 7+) | Supported | | New Architecture | 24+ | Supported |
The library uses its own self-contained native module and has no dependency on react-native-wifi-p2p or other third-party native wrappers.
