npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

munim-wifi

v0.3.1

Published

Fast Wi-Fi scanning, connection, and network information for Expo and React Native, powered by Nitro Modules

Readme

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

🚀 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.