Skip to content
Lucent
SEARCH LUCENT

Search guides, APIs, and examples.

GitHub

Very early and experimental. The language, the generated native code and every package API change without notice. Do not use Lucent in production.

Network state

@react-native-community/netinfo's API on the Network framework and Android's ConnectivityManager, with change events sent to JavaScript.

  • iOS: the Network framework's C functions. The path monitor's update handler is a Lucent function passed as a block, called on the main queue.
  • Android: Watcher extends ConnectivityManager.NetworkCallback, an SDK class, and overrides three of its methods.
  • addEventListener sends each change to a JS callback and returns an id that removeEventListener takes back.
netInfo.lucent.ts
import { PLATFORM } from "lucent:platform";
import {
nw_interface_type_t,
nw_path_get_status,
nw_path_monitor_cancel,
nw_path_monitor_create,
nw_path_monitor_set_queue,
nw_path_monitor_set_update_handler,
nw_path_monitor_start,
nw_path_status_t,
nw_path_uses_interface_type,
type OS_nw_path,
type OS_nw_path_monitor,
} from "lucent:ios/Network";
import { mainQueue } from "lucent:ios";
import {
ConnectivityManager,
ConnectivityManager_NetworkCallback,
Network,
NetworkCapabilities,
} from "lucent:android/android.net";
import { appContext, available } from "lucent:android";
export interface NetInfoDetails {
isConnectionExpensive: boolean;
}
export interface NetInfoState {
type: string;
isConnected: boolean;
isInternetReachable: boolean | null;
details: NetInfoDetails | null;
}
let nextId = 1;
// --- iOS: netinfo reads SCNetworkReachability (wifi unless the path is cellular) ---
function pathState(path: OS_nw_path): NetInfoState {
if (nw_path_get_status(path) !== nw_path_status_t.nw_path_status_satisfied) {
return { type: "none", isConnected: false, isInternetReachable: false, details: null };
}
const cellular = nw_path_uses_interface_type(
path,
nw_interface_type_t.nw_interface_type_cellular,
);
// Like SCNetworkReachability, which netinfo reads: only cellular is told apart.
return {
type: cellular ? "cellular" : "wifi",
isConnected: true,
isInternetReachable: null,
details: { isConnectionExpensive: cellular },
};
}
const monitors = new Map<number, OS_nw_path_monitor>();
function watchPath(onState: (state: NetInfoState) => void): number {
const id = nextId++;
const monitor = nw_path_monitor_create();
nw_path_monitor_set_update_handler(monitor, (path) => onState(pathState(path)));
nw_path_monitor_set_queue(monitor, mainQueue());
monitors.set(id, monitor);
nw_path_monitor_start(monitor);
return id;
}
function stopPath(id: number): void {
const monitor = monitors.get(id);
monitors.delete(id);
if (monitor) nw_path_monitor_cancel(monitor);
}
function fetchPath(): Promise<NetInfoState> {
return new Promise((resolve) => {
let id = 0;
id = watchPath((state) => {
stopPath(id);
resolve(state);
});
});
}
// --- Android -----------------------------------------------------------------
function connectivity(): ConnectivityManager {
const m = appContext().getSystemService(ConnectivityManager);
if (!m) throw new Error("ConnectivityManager is not available");
return m;
}
function capabilitiesState(capabilities: NetworkCapabilities | null): NetInfoState {
if (!capabilities)
return { type: "none", isConnected: false, isInternetReachable: false, details: null };
const has = (transport: number) => capabilities.hasTransport(transport);
const type = has(NetworkCapabilities.TRANSPORT_BLUETOOTH)
? "bluetooth"
: has(NetworkCapabilities.TRANSPORT_CELLULAR)
? "cellular"
: has(NetworkCapabilities.TRANSPORT_ETHERNET)
? "ethernet"
: has(NetworkCapabilities.TRANSPORT_WIFI)
? "wifi"
: has(NetworkCapabilities.TRANSPORT_VPN)
? "vpn"
: "unknown";
const suspended = available("android", 28)
? !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
: false;
let reachable =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
!suspended;
if (has(NetworkCapabilities.TRANSPORT_VPN))
reachable = reachable && capabilities.getLinkDownstreamBandwidthKbps() !== 0;
const isConnected = type !== "unknown";
return {
type,
isConnected,
isInternetReachable: reachable,
details: isConnected
? { isConnectionExpensive: connectivity().isActiveNetworkMetered() }
: null,
};
}
function currentNetwork(): NetInfoState {
const m = connectivity();
const network = m.getActiveNetwork();
return capabilitiesState(network ? m.getNetworkCapabilities(network) : null);
}
/** The default network's changes, as netinfo follows them. */
class Watcher extends ConnectivityManager_NetworkCallback {
constructor(private readonly onState: (state: NetInfoState) => void) {
super();
}
onAvailable(_network: Network): void {
this.onState(currentNetwork());
}
onLost(_network: Network): void {
this.onState(capabilitiesState(null));
}
onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities): void {
this.onState(capabilitiesState(capabilities));
}
}
const watchers = new Map<number, Watcher>();
// --- The module --------------------------------------------------------------
export async function fetch(): Promise<NetInfoState> {
return PLATFORM === "ios" ? fetchPath() : currentNetwork();
}
/** Calls `listener` with the state now and on each change, until removeEventListener(id). */
export async function addEventListener(listener: (state: NetInfoState) => void): Promise<number> {
if (PLATFORM === "ios") {
return watchPath(listener);
} else {
const id = nextId++;
const watcher = new Watcher(listener);
watchers.set(id, watcher);
connectivity().registerDefaultNetworkCallback(watcher);
listener(currentNetwork());
return id;
}
}
export async function removeEventListener(id: number): Promise<void> {
if (PLATFORM === "ios") {
stopPath(id);
} else {
const watcher = watchers.get(id);
watchers.delete(id);
if (watcher) connectivity().unregisterNetworkCallback(watcher);
}
}

Source: netInfo.lucent.ts.