expo-nearby-connections
v1.1.0
Published
P2P connections library for Expo. Supports Android and iOS.
Maintainers
Readme
expo-nearby-connections
An Expo library for peer-to-peer connections between nearby devices. Uses Google Nearby Connections on Android and Apple Multipeer Connectivity on iOS.
Note: Cross-platform P2P between Android and iOS is not supported.
Compatible Versions
| expo-nearby-connections | expo | react-native | | :---------------------: | :---: | :----------: | | 1.1.0 | 55 | 0.83.x | | 1.0.0 | 51 | 0.73.x |
New Architecture required. expo-nearby-connections 1.1.0 uses Nitro Modules, which requires New Architecture (enabled by default since Expo SDK 52).
Installation
npx expo install expo-nearby-connections react-native-nitro-modulesOr with npm/yarn/pnpm:
npm install expo-nearby-connections react-native-nitro-modules
# or
yarn add expo-nearby-connections react-native-nitro-modules
# or
pnpm add expo-nearby-connections react-native-nitro-modulesSetup
1. Add the config plugin
In your app.json or app.config.ts:
{
"plugins": [
[
"expo-nearby-connections",
{
"bonjourServicesName": "my-app",
"localNetworkUsagePermissionText": "$(PRODUCT_NAME) needs local network access to discover nearby devices",
"bluetoothUsagePermissionText": "$(PRODUCT_NAME) uses Bluetooth to discover and connect to nearby devices"
}
]
]
}All plugin options are optional. Without options, default permission strings are used.
| Option | Platform | Description |
| ------ | -------- | ----------- |
| bonjourServicesName | iOS | Bonjour service name (defaults to app name) |
| localNetworkUsagePermissionText | iOS | NSLocalNetworkUsageDescription |
| bluetoothUsagePermissionText | iOS | NSBluetoothAlwaysUsageDescription |
2. Prebuild
npx expo prebuild --cleanRun prebuild again whenever you change the plugin config.
Permissions
This library does not handle runtime permissions. Use react-native-permissions or similar.
iOS
import { PERMISSIONS, checkMultiple, requestMultiple } from "react-native-permissions";
const permissions = [
PERMISSIONS.IOS.BLUETOOTH,
PERMISSIONS.IOS.LOCAL_NETWORK,
];Android
import { PERMISSIONS, checkMultiple, requestMultiple } from "react-native-permissions";
const permissions = [
PERMISSIONS.ANDROID.ACCESS_COARSE_LOCATION,
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
PERMISSIONS.ANDROID.BLUETOOTH_ADVERTISE,
PERMISSIONS.ANDROID.BLUETOOTH_CONNECT,
PERMISSIONS.ANDROID.BLUETOOTH_SCAN,
PERMISSIONS.ANDROID.NEARBY_WIFI_DEVICES,
];API
Types
enum Strategy {
P2P_CLUSTER = 1, // many-to-many mesh
P2P_STAR = 2, // one hub, multiple spokes (default)
P2P_POINT_TO_POINT = 3, // 1-to-1
}
interface BasePeer {
peerId: string;
name: string;
}Advertise
startAdvertise(name, strategy?)
Starts broadcasting so nearby discoverers can find this device.
import { startAdvertise, Strategy } from "expo-nearby-connections";
const peerId = await startAdvertise("My Device", Strategy.P2P_STAR);stopAdvertise()
import { stopAdvertise } from "expo-nearby-connections";
await stopAdvertise();Discover
startDiscovery(name, strategy?)
Starts scanning for nearby advertisers.
import { startDiscovery, Strategy } from "expo-nearby-connections";
const peerId = await startDiscovery("My Device", Strategy.P2P_STAR);stopDiscovery()
import { stopDiscovery } from "expo-nearby-connections";
await stopDiscovery();Connection
requestConnection(advertisePeerId)
Sends a connection request to an advertiser found via onPeerFound.
import { requestConnection } from "expo-nearby-connections";
await requestConnection(peerId);acceptConnection(targetPeerId)
Accepts an incoming connection request (called on the advertiser side).
import { acceptConnection } from "expo-nearby-connections";
await acceptConnection(peerId);rejectConnection(targetPeerId)
Rejects an incoming connection request.
import { rejectConnection } from "expo-nearby-connections";
await rejectConnection(peerId);disconnect(targetPeerId?)
Disconnects from a connected peer.
- Android: if
targetPeerIdis provided, disconnects only that endpoint; omitting it callsstopAllEndpoints(). - iOS:
targetPeerIdis always ignored —MCSession.disconnect()terminates the entire session.
import { disconnect } from "expo-nearby-connections";
await disconnect(peerId); // Android: disconnect specific peer
await disconnect(); // Android: disconnect all / iOS: disconnect sessionMessaging
sendText(targetPeerId, text)
Sends a UTF-8 text message to a connected peer.
import { sendText } from "expo-nearby-connections";
await sendText(peerId, "Hello!");Events
All event listeners return an Unsubscribe function. Call it to remove the listener.
onPeerFound(callback)
Fires when a discoverer finds an advertiser.
import { onPeerFound } from "expo-nearby-connections";
const unsubscribe = onPeerFound(({ peerId, name }) => {
console.log("Found:", name);
});
unsubscribe(); // cleanuponPeerLost(callback)
Fires when a previously discovered advertiser goes out of range.
import { onPeerLost } from "expo-nearby-connections";
const unsubscribe = onPeerLost(({ peerId }) => {
// remove from list
});onInvitationReceived(callback)
Fires on the advertiser when a discoverer calls requestConnection.
import { onInvitationReceived } from "expo-nearby-connections";
const unsubscribe = onInvitationReceived(({ peerId, name }) => {
// prompt user to accept/reject
});onConnected(callback)
Fires on both sides when a connection is fully established.
import { onConnected } from "expo-nearby-connections";
const unsubscribe = onConnected(({ peerId, name }) => {
// connection ready
});onDisconnected(callback)
Fires when a peer disconnects.
import { onDisconnected } from "expo-nearby-connections";
const unsubscribe = onDisconnected(({ peerId }) => {
// remove from connected list
});onTextReceived(callback)
Fires when a text message arrives from a connected peer.
import { onTextReceived } from "expo-nearby-connections";
const unsubscribe = onTextReceived(({ peerId, text }) => {
console.log("Message from", peerId, ":", text);
});Usage example
Advertiser side
import { useEffect, useState } from "react";
import {
startAdvertise,
stopAdvertise,
onInvitationReceived,
onConnected,
onDisconnected,
acceptConnection,
} from "expo-nearby-connections";
function AdvertiserScreen() {
const [myPeerId, setMyPeerId] = useState<string>();
useEffect(() => {
startAdvertise("My Device").then(setMyPeerId);
return () => { stopAdvertise(); };
}, []);
useEffect(() => {
const unsub = onInvitationReceived(({ peerId }) => {
acceptConnection(peerId);
});
return unsub;
}, []);
}Discoverer side
import { useEffect, useState } from "react";
import {
startDiscovery,
stopDiscovery,
onPeerFound,
onPeerLost,
requestConnection,
type BasePeer,
} from "expo-nearby-connections";
function DiscovererScreen() {
const [peers, setPeers] = useState<BasePeer[]>([]);
useEffect(() => {
startDiscovery("My Device");
return () => { stopDiscovery(); };
}, []);
useEffect(() => {
const unsubFound = onPeerFound((peer) => {
setPeers((prev) => [...prev, peer]);
});
const unsubLost = onPeerLost(({ peerId }) => {
setPeers((prev) => prev.filter((p) => p.peerId !== peerId));
});
return () => { unsubFound(); unsubLost(); };
}, []);
const connect = (peerId: string) => requestConnection(peerId);
}Development
# from repo root
pnpm install
pnpm build
# run example app
cd example
pnpm install
pnpm prebuild --clean --no-install
# iOS
pnpm ios
# Android
pnpm androidContributing
Contributions are welcome. Please open an issue first for major changes.
See CHANGELOG.md for release history.
