Skip to content

Repository files navigation

Munim Technologies Wi-Fi

munim-wifi

Package version License: Apache-2.0 Monthly downloads Total downloads

React Native Expo iOS Android Nitro Modules

Works with Expo  ‒  Read the Documentation  ‒  Report Issues

Follow Munim Technologies

Munim Technologies on GitHub Β  Munim Technologies on LinkedIn Β  Munim Technologies website

Introduction

munim-wifi is a comprehensive React Native Wi-Fi library for nearby-network discovery, current-network information, connection flows, and Wi-Fi fingerprinting. It exposes SSIDs, BSSIDs, signal strength, frequencies, channels, security information, local IP data, and platform-native connect/disconnect behavior where the operating system permits it.

Fully compatible with Expo! It includes an Expo config plugin and a managed Expo example app. Because the package contains native code, Expo projects must use a development build rather than Expo Go.

Built with React Native's Nitro Modules architecture using Swift on iOS, Kotlin on Android, generated native bindings, and callback-based continuous results without a legacy React Native event bridge.

Note: Wi-Fi is heavily platform-gated. Android exposes nearby scans but throttles their frequency. Ordinary iOS apps cannot perform general Wi-Fi scans, so iOS returns the current network when Apple allows access. Unsupported data is returned as null instead of being fabricated.

Table of contents

πŸ“š Documentation

Learn about building Wi-Fi-aware apps in our documentation!

πŸš€ Features

Wi-Fi Discovery

  • πŸ“‘ Nearby Network Scanning: Retrieve Android scan results without blocking a native thread.
  • πŸ“Ά Signal Information: Read RSSI, frequency, and calculated 2.4/5/6/60 GHz channel information on Android.
  • πŸ” Security Details: Read capabilities and an easy-to-use secure/open flag.
  • πŸ”„ Continuous Results: Subscribe to result batches or individual networks through Nitro callbacks.
  • 🧭 Wi-Fi Fingerprinting: Capture visible networks with a millisecond timestamp.

Network Management

  • πŸ”Œ Native Connection Flows: Android WifiNetworkSpecifier and iOS NEHotspotConfiguration.
  • πŸ“± Current Network Information: Read SSID, BSSID, IP address, gateway, DNS, and subnet data where available.
  • 🌐 Local Routing: Android 10+ binds the app process to the approved requested network until disconnect().
  • βœ… Explicit Failures: Invalid options, missing permissions, disabled Wi-Fi, timeouts, and unsupported WEP flows reject clearly.

Additional Features

  • πŸ“± Cross-platform: One TypeScript API with honest platform-specific results.
  • 🎯 TypeScript Support: Full result, option, callback, and HybridObject types.
  • ⚑ High Performance: Nitro Modules with generated Swift/Kotlin/C++ bindings.
  • πŸš€ Expo Compatible: Managed config plugin and Expo 57 example project.
  • πŸ” Permission Handling: Android runtime permission requests and real iOS location authorization.
  • πŸ§ͺ Release Verification: Package, example, iOS, and Android release-candidate checks.

Platform Support Matrix

Capability iOS Android Notes
Nearby-network scan ⚠️ Current network only βœ… Full Ordinary iOS apps cannot enumerate nearby Wi-Fi networks.
SSID and BSSID βœ… βœ… iOS requires the Wi-Fi Information entitlement plus an Apple access criterion.
RSSI ❌ βœ… Android returns dBm.
Frequency and channel ❌ βœ… Android covers 2.4, 5, 6, and 60 GHz channel calculations.
Capabilities/security ⚠️ Security state only βœ… iOS does not expose Android-style capability strings.
Current network βœ… βœ… Values can be hidden by permissions or OS privacy behavior.
Local IPv4 address βœ… βœ… Returns null when no Wi-Fi interface is available.
Connect βœ… βœ… Both platforms use system-controlled user-consent flows.
Disconnect ⚠️ Removes app configuration βœ… iOS cannot force-disconnect arbitrary saved networks.
Continuous scan ⚠️ One current-network result βœ… Android scan throttling still applies.
Wi-Fi fingerprint ⚠️ Current network only βœ… No location is inferred by the library.
Security type ⚠️ Coarse (open/WEP/personal/enterprise) βœ… Full Android classifies WPA2/WPA3/OWE/EAP/Passpoint from scan capabilities.
Local network request βœ… joinOnce configuration βœ… Android 10+ specifier Structured ConnectionOutcome on both platforms.
Persistent configuration βœ… NEHotspotConfiguration ⚠️ Via network suggestion configureNetwork().
Network suggestions ❌ unsupported outcome βœ… Android 10+ NEHotspotConfiguration is the iOS analog.
Wi-Fi settings intent ❌ βœ… Android 10+ requestUserSavedNetwork() opens the system panel.
Local-only hotspot ❌ βœ… Android 8+ Returns generated SSID/passphrase/security.
Network diagnostics ⚠️ Path-level βœ… Capability + link level validated/captivePortal are Android-only.
Network observer βœ… NWPathMonitor βœ… Default network callback Continuous NetworkDiagnostics updates.
Capability report βœ… βœ… getWifiCapabilityStatus() includes permission states.

Platform support can vary by OS version, hardware, permission state, foreground/background state, and device-management policy.

πŸ“¦ Installation

React Native CLI

npm install munim-wifi react-native-nitro-modules
# or
yarn add munim-wifi react-native-nitro-modules

Expo

npx expo install munim-wifi react-native-nitro-modules

Important: This package requires a native Expo development build and does not work in Expo Go. After installing, run npx expo run:ios, npx expo run:android, or create a development build with EAS.

Add the included config plugin to app.json:

{
  "expo": {
    "plugins": [
      [
        "munim-wifi",
        {
          "locationPermission": "Allow this app to find nearby Wi-Fi networks."
        }
      ]
    ]
  }
}

The plugin adds the Android Wi-Fi, location, and Nearby Wi-Fi Devices permissions; the iOS location description; and the iOS Access Wi-Fi Information and Hotspot Configuration entitlements.

Generate or rebuild native projects after changing the plugin configuration:

npx expo prebuild
npx expo run:ios
# or
npx expo run:android

iOS Setup

Bare React Native apps must enable these capabilities in Xcode:

  • Access Wi-Fi Information
  • Hotspot Configuration

Add a location usage message to Info.plist:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses location permission to access Wi-Fi information.</string>

Android Setup

Bare React Native apps should merge these permissions into the application manifest:

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />

The Expo config plugin adds them automatically.

Permissions and OS Behavior

iOS current-network access

Apple's NEHotspotNetwork.fetchCurrent() returns a network only when the app has the Access Wi-Fi Information entitlement and meets at least one qualifying condition, such as precise-location authorization, a network configured by the app, an active VPN configuration, or an active DNS settings configuration. See Apple's fetchCurrent documentation.

disconnect() can remove only a Wi-Fi configuration created by the app. It cannot remove or force-disconnect a network configured by the user or another app.

Android scan and connection access

Wi-Fi scans and scan results require precise-location permission. Android 13+ connection management also uses the Nearby Wi-Fi Devices runtime permission. See Android Wi-Fi permissions.

Android throttles WifiManager.startScan(). Foreground apps can still receive cached results when the OS declines a fresh scan, so the timestamp field records when this package converted the result, not when the radio last observed it.

Android 10+ connections use WifiNetworkSpecifier. The OS presents a system approval flow and may create a local-only connection. The library binds the app process to the approved network until disconnect() so app traffic can reach that network.

⚑ Quick Start

Basic Usage - Scan Networks

import {
  getCurrentNetwork,
  isWifiEnabled,
  requestWifiPermission,
  scanNetworks,
} from 'munim-wifi'

const enabled = await isWifiEnabled()
if (!enabled) throw new Error('Wi-Fi is unavailable')

const hasPermission = await requestWifiPermission()
if (!hasPermission) throw new Error('Wi-Fi permission was not granted')

const [current, networks] = await Promise.all([
  getCurrentNetwork(),
  scanNetworks({ maxResults: 30, timeout: 10_000 }),
])

console.log('Current network:', current)
networks.forEach((network) => {
  console.log(network.ssid, network.bssid, network.rssi, network.channel)
})

Continuous Scanning

import {
  addNetworksFoundListener,
  addScanErrorListener,
  startScan,
  stopScan,
} from 'munim-wifi'

const removeResults = addNetworksFoundListener((networks) => {
  console.log('Updated networks:', networks)
})
const removeError = addScanErrorListener(console.warn)

startScan({ interval: 30_000, maxResults: 30 })

// Later:
stopScan()
removeResults()
removeError()

Connect to a Network

import { connectToNetwork, disconnect } from 'munim-wifi'

await connectToNetwork({
  ssid: 'Workshop Wi-Fi',
  password: 'correct-horse-battery-staple',
  timeout: 30_000,
})

// Release/remove the app-managed connection later.
await disconnect()

Android 10+ and iOS both show system-controlled approval UI. WEP is unsupported on Android 10+.

πŸ”§ API Reference

Discovery Functions

isWifiEnabled()

Checks whether Wi-Fi appears available to the app.

Returns: Promise<boolean>

On iOS this is inferred from current-network access because Apple does not expose a public Wi-Fi enabled-state API.

requestWifiPermission()

Requests precise-location and Nearby Wi-Fi Devices permissions on supported Android versions. On iOS, requests When In Use location authorization when it has not been determined.

Returns: Promise<boolean>

scanNetworks(options?)

Runs one Android scan or one iOS current-network lookup.

Parameters:

  • maxResults? (number): Positive integer result limit.
  • timeout? (number): Android timeout from 250 to 30,000 milliseconds.

Returns: Promise<WifiNetwork[]>

startScan(options?)

Starts repeated Android scans. On iOS, emits one current-network result because general scanning is unavailable.

Parameters:

  • maxResults? (number): Positive integer result limit.
  • interval? (number): 10,000 to 600,000 milliseconds. Defaults to 30,000.
  • timeout? (number): Validation-compatible one-shot timeout value.

Use addNetworksFoundListener(), addNetworkFoundListener(), or addScanErrorListener() before starting.

stopScan()

Stops continuous Android scanning and releases its broadcast receiver.

getSSIDs()

Returns visible SSIDs from the current scan information.

Returns: Promise<string[]>

getWifiFingerprint()

Returns visible/current networks and a millisecond timestamp.

Returns: Promise<WifiFingerprint>

Network Information Functions

getRSSI(ssid)

Returns Android signal strength in dBm or null. iOS returns null.

getBSSID(ssid)

Returns the BSSID matching an SSID or null.

getChannelInfo(ssid)

Returns Android { channel, frequency } data or null. iOS returns null.

getNetworkInfo(ssid)

Returns complete information for the first matching SSID or null.

getCurrentNetwork()

Returns CurrentNetworkInfo or null. Depending on the platform, it can contain SSID, BSSID, IP address, subnet mask, gateway, and DNS servers.

getIPAddress()

Returns the current Wi-Fi interface's local IPv4 address or null.

Connection Functions

connectToNetwork(options)

Starts the native connection flow.

Parameters:

  • ssid (string): Required network name, limited to 32 UTF-8 bytes.
  • password? (string): WPA/WPA2 or WEP password, limited to 64 UTF-8 bytes.
  • isWEP? (boolean): Legacy WEP mode; unsupported on Android 10+.
  • security? (WifiSecurityType): Optional explicit security type. When omitted, security is inferred from password/isWEP (open without a password, WPA2 with one). Pass 'wpa3' to allow short SAE passphrases and use setWpa3Passphrase on Android.
  • bssid? (string): Optional Android 10+ BSSID constraint.
  • joinOnce? (boolean): Temporary connection behavior. Defaults to true; set explicitly to false to retain a saved configuration where supported.
  • timeout? (number): Connection timeout from 5,000 to 120,000 milliseconds on iOS and Android.

Returns: Promise<void>

iOS settles each attempt once, verifies the resulting SSID when public APIs expose it, and removes only a newly created persistent configuration after a failed or timed-out attempt. Existing saved configurations are preserved.

disconnect()

Android releases the requested network and clears process binding. iOS removes the app-created configuration for the current SSID.

Returns: Promise<void>

Structured Connection Functions

These functions never reject on ordinary platform limitations β€” they resolve with a structured outcome whose status explains what happened ('connected' | 'configured' | 'presented' | 'released' | 'unsupported' | 'cancelled' | 'failed').

requestLocalNetwork(options)

Requests a temporary, app-scoped connection to a nearby network.

  • Android 10+: WifiNetworkSpecifier + ConnectivityManager.requestNetwork. Set bindProcess: true to route this process's traffic through the network. The returned leaseId releases the request later.
  • iOS: NEHotspotConfiguration with joinOnce: true. The returned leaseId is the SSID.

Parameters: { ssid, security, bssid?, timeout?, bindProcess? } where security is one of { type: 'open' } | { type: 'owe' } | { type: 'wep', passphrase } | { type: 'wpa2', passphrase } | { type: 'wpa3', passphrase }.

Returns: Promise<ConnectionOutcome> β€” status: 'connected' on success with mode: 'localNetwork'.

configureNetwork(options)

Persists a network configuration.

  • iOS: persistent NEHotspotConfiguration (configurationId is the SSID).
  • Android 10+: routed through a network suggestion, since Android has no direct managed-configuration equivalent.

Returns: Promise<ConnectionOutcome> β€” status: 'configured' with mode: 'managedConfiguration'.

requestUserSavedNetwork(options?)

  • Android 10+: opens the system Wi-Fi panel (or the Android 11+ "add networks" flow when options are given) and resolves status: 'presented'.
  • iOS: resolves status: 'unsupported'.

releaseConnection(leaseOrConfigurationId)

Releases a requestLocalNetwork lease (Android unregisters the callback and unbinds the process) or removes a configuration (removeConfiguration(forSSID:) on iOS). Resolves status: 'released'.

addNetworkSuggestion(options) / removeNetworkSuggestion(options) / getNetworkSuggestionStatus(options)

Android 10+ WifiManager network suggestions with open, owe, wpa2, and wpa3 support plus hidden and appInteractionRequired flags. Status values include 'added' | 'alreadyExists' | 'removed' | 'notFound' | 'active' | 'inactive'. On iOS these resolve status: 'unsupported' β€” NEHotspotConfiguration (configureNetwork) is the closest analog. Enterprise and Passpoint credentials resolve 'unsupported' on both platforms.

Returns: Promise<SuggestionOutcome>

startLocalOnlyHotspot() / stopLocalOnlyHotspot(reservationId)

Android 8+ local-only hotspot (requires location and, on Android 13+, Nearby Wi-Fi Devices permissions). The outcome carries the generated ssid, passphrase, and securityType. iOS resolves status: 'unsupported'.

Returns: Promise<HotspotOutcome>

Capability and Diagnostics Functions

getWifiCapabilityStatus()

Reports per-capability availability (scan, localNetworkRequest, managedConfiguration, networkSuggestions, userSavedNetworkIntent, localOnlyHotspot, wifiDirect, wifiAware, wifiRtt) and permission states (locationPermission, nearbyWifiPermission, wifiInformationPermission) for the current OS version.

Returns: Promise<WifiCapabilityStatus>

getNetworkDiagnostics()

One-shot snapshot of the default network.

  • Android: NetworkCapabilities (validated, captivePortal, metered, constrained) plus LinkProperties (interface, addresses, DNS servers, routes, MTU).
  • iOS: NWPathMonitor (metered from isExpensive, constrained from isConstrained); validated/captivePortal are not detectable and stay unset.

Returns: Promise<NetworkDiagnostics>

startNetworkObserver(callback) / stopNetworkObserver() / addNetworkObserverListener(callback)

Continuous NetworkDiagnostics updates as the default network appears, changes capabilities, or is lost (state: 'available' | 'lost' | 'unavailable'). addNetworkObserverListener multiplexes many JS listeners over one native observer and returns a cleanup function.

Events

API/event Payload Notes
addNetworkFoundListener(callback) WifiNetwork Called once for every network in a result batch.
addNetworksFoundListener(callback) WifiNetwork[] Called once per continuous result batch.
addScanErrorListener(callback) string Continuous-scan error message.
addEventListener('networkFound', callback) WifiNetwork Generic listener alias.
addEventListener('networksFound', callback) WifiNetwork[] Generic listener alias.
addEventListener('scanError', callback) string Generic listener alias.

Each listener function returns a cleanup function. addListener() and removeListeners() remain deprecated compatibility shims.

Types

type WifiSecurityType =
  | 'open'
  | 'owe'
  | 'wep'
  | 'wpa2'
  | 'wpa3'
  | 'enterprise'
  | 'passpoint'
  | 'unknown'

interface WifiNetwork {
  ssid: string
  bssid: string
  rssi?: number
  frequency?: number
  channel?: number
  capabilities?: string
  isSecure?: boolean
  securityType: WifiSecurityType
  timestamp?: number
}

interface CurrentNetworkInfo {
  ssid: string
  bssid: string
  securityType: WifiSecurityType
  ipAddress?: string
  subnetMask?: string
  gateway?: string
  dnsServers?: string[]
}

interface WifiFingerprint {
  networks: WifiNetwork[]
  timestamp: number
  location?: { latitude?: number; longitude?: number }
}

interface ConnectionOutcome {
  status: ConnectionStatus // 'connected' | 'configured' | 'presented' | 'released' | 'unsupported' | 'cancelled' | 'failed'
  mode: ConnectionMode // 'localNetwork' | 'managedConfiguration' | 'userSavedNetwork'
  ssid?: string
  leaseId?: string
  configurationId?: string
  boundProcess: boolean
  message?: string
}

interface SuggestionOutcome {
  status: SuggestionStatus
  suggestionId?: string
  message?: string
}

interface HotspotOutcome {
  status: HotspotStatus // 'started' | 'stopped' | 'unsupported' | 'failed'
  reservationId?: string
  ssid?: string
  passphrase?: string
  securityType: WifiSecurityType
  message?: string
}

interface NetworkDiagnostics {
  timestamp: number
  state: NetworkState // 'available' | 'lost' | 'unavailable'
  validated?: boolean
  captivePortal?: boolean
  metered?: boolean
  constrained?: boolean
  currentNetwork?: CurrentNetworkInfo
  linkProperties?: NetworkLinkProperties
}

All public result, option, callback, and HybridObject types are exported from the package.

πŸ“– Usage Examples

Wi-Fi Fingerprint

import { getWifiFingerprint, requestWifiPermission } from 'munim-wifi'

if (await requestWifiPermission()) {
  const fingerprint = await getWifiFingerprint()
  console.log('Captured at', new Date(fingerprint.timestamp))
  fingerprint.networks.forEach(({ ssid, bssid, rssi }) => {
    console.log(ssid, bssid, rssi)
  })
}

React Network Scanner

import { useEffect, useState } from 'react'
import { Button, FlatList, Text, View } from 'react-native'
import {
  requestWifiPermission,
  scanNetworks,
  type WifiNetwork,
} from 'munim-wifi'

export function NetworkScanner() {
  const [networks, setNetworks] = useState<WifiNetwork[]>([])
  const [message, setMessage] = useState('Ready')

  const scan = async () => {
    try {
      if (!(await requestWifiPermission())) {
        setMessage('Permission denied')
        return
      }
      setNetworks(await scanNetworks({ maxResults: 50, timeout: 10_000 }))
      setMessage('Scan complete')
    } catch (error) {
      setMessage(error instanceof Error ? error.message : String(error))
    }
  }

  useEffect(() => () => setNetworks([]), [])

  return (
    <View>
      <Button title="Scan Wi-Fi" onPress={scan} />
      <Text>{message}</Text>
      <FlatList
        data={networks}
        keyExtractor={(network) => network.bssid || network.ssid}
        renderItem={({ item }) => (
          <Text>{item.ssid}: {item.rssi ?? 'β€”'} dBm</Text>
        )}
      />
    </View>
  )
}

πŸ” Troubleshooting

Common Issues

  • The scan returns no Android networks: Confirm Wi-Fi is enabled, precise-location permission is granted, and device Location Services are enabled. Android can also throttle repeated scans.
  • Android 13+ connection throws a permission error: Request Nearby Wi-Fi Devices permission with requestWifiPermission() before connecting.
  • iOS returns null for the current network: Verify the Access Wi-Fi Information entitlement, precise-location authorization, and Apple's fetchCurrent() eligibility conditions.
  • iOS returns no RSSI/channel/frequency: Those values are not exposed to ordinary iOS apps. This is expected.
  • The Android connection cannot reach a local device: Keep the connection active and do not call disconnect() until local traffic is finished; the package binds the app process to the approved network.
  • WEP fails on modern Android: WifiNetworkSpecifier does not support WEP. Use WPA2/WPA3 or an open network.

Expo-Specific Issues

  • This package does not work in Expo Go; create a development build.
  • Run npx expo prebuild --clean after changing plugin options or upgrading the package.
  • If iOS capabilities are missing, inspect the generated .entitlements file after prebuild.
  • If Android permissions are missing, inspect the merged application manifest rather than only the library manifest.

Debug Mode

The example app in example/ requests permission, displays current-network information, scans, and renders native result fields. Run it with:

npm install
npm --workspace munim-wifi-example run prebuild
npm --workspace munim-wifi-example run ios
# or
npm --workspace munim-wifi-example run android

πŸ‘ Contributing

See CONTRIBUTING.md for contribution guidelines. Before opening a pull request, run:

npm install
npm run codegen
npm run build
npm run typecheck:example
npm pack --dry-run

Do not edit files in nitrogen/generated directly. Change src/specs/munim-wifi.nitro.ts and rerun npm run codegen.

Local release (maintainers)

Releases run locally and do not require GitHub Actions. On the configured maintainer Mac, npm run release:local reads the npm publishing token from macOS Keychain and the GitHub token from the authenticated GitHub CLI session, then runs semantic-release. The credentials are never stored in this repository.

Use npm run release:local -- --dry-run to verify the next release without publishing it.

πŸ“„ License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

About

React Native Wi-Fi scanning, current-network details, connection management, Expo config plugin, and Nitro Modules for iOS and Android

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages