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

@dmytromykhailiuk/network-connection

v1.1.0

Published

Real network state for the browser — healthcheck-verified isOnline, promise helpers for waiting on connectivity and auto-restarting interrupted work. navigator.onLine lies; this doesn't. Zero dependencies.

Downloads

388

Readme

@dmytromykhailiuk/network-connection

Real network state for the browser — healthcheck-verified isOnline, promise helpers for waiting on connectivity and auto-restarting interrupted work. navigator.onLine lies; this doesn't. Zero dependencies.

Full documentation: open Docs in a browser — every option, with examples, a table of contents and cross-links. This README is the short form.

⚠️ The rule that makes it work: a negative signal is trusted, a positive one is verified. The offline event flips isOnline to false immediately — a definite no. Everything that claims "yes" — the online event, page startup, a ping tick — must prove it with a delivered healthcheck response first. And await init() once at startup: every member throws until then, because a made-up default is exactly the kind of plausible-looking lie this library exists to kill.

Built for apps that keep working when the network doesn't — PWAs, offline-first tools, anything with a sync queue or a long-running upload. They all need the answer to one question: is the network actually there? navigator.onLine is not that answer. Refresh an installed PWA while offline: the service worker serves the shell from cache, the page boots, and navigator.onLine reports true — the Wi-Fi interface is up, so the browser is technically not wrong, just useless. The offline event fired in the previous page, before the refresh threw that listener away. And a connected-but-dead network (captive portal, dead uplink) keeps onLine at true forever without a single event.

So this library never reads navigator.onLine. The state changes in exactly three ways: the offline event sets it to false synchronously; a successful healthcheck request sets it to true; a failed one sets it to false. On top of that sit promise helpers that turn connectivity-aware flows into plain await lines.

Install

npm i @dmytromykhailiuk/network-connection

Quick start

import { NetworkConnection } from "@dmytromykhailiuk/network-connection";

// resolves after the first healthcheck — isOnline is truthful from here on
await NetworkConnection.init("/api/health", {
  pingInterval: 30_000, // optional: re-verify every 30 s while online
});

NetworkConnection.isOnline;                  // boolean — the real state

// react to every change; returns the unsubscribe function
const unsubscribe = NetworkConnection.subscribe((isOnline) => {
  offlineBanner.hidden = isOnline;
});

await NetworkConnection.continueWhenOnline(); // park a flow until the network is back

// work that must survive connection drops: on a network failure it
// waits for the reconnect and starts over
const orders = await NetworkConnection.restartIfNotFinishedWhenOnline(
  () => fetch("/api/orders").then((res) => res.json()),
);

API

NetworkConnection.init(healthcheckUrl, {
  pingInterval?: number;       // re-check every N ms while online — detects silent losses
  healthcheckTimeout?: number; // abort the check after N ms and count it as failed (5000)
  method?: "HEAD" | "GET";     // healthcheck request method ("GET")
});                            // Promise<void> — resolves after the first check

NetworkConnection.isOnline;                        // boolean; throws before init()
NetworkConnection.subscribe(listener);             // () => void — call it to unsubscribe
NetworkConnection.continueWhenOnline();            // resolves when online (now or later)
NetworkConnection.continueWhenOffline();           // mirror
NetworkConnection.afterOnlineBack();               // resolves after a disconnect → reconnect cycle
NetworkConnection.restartIfNotFinishedWhenOnline(fn); // verify, run fn, retry across offline periods
NetworkConnection.destroy();                       // undo init(); rejects pending waiters

Calling init() twice throws — call destroy() first to re-configure.

subscribe

const unsubscribe = NetworkConnection.subscribe((isOnline) => {
  offlineBanner.hidden = isOnline;
});

unsubscribe(); // detach — calling it again is a no-op

Any number of listeners can be subscribed at once; one transition calls them all, in subscription order, with the new value. Only changes are delivered — the listener is not called on subscription, because the current value is already there synchronously. When a listener needs an initial run, pass it yourself: listener(NetworkConnection.isOnline).

Because the state is healthcheck-verified, a listener fires exactly when the truth changes: an offline event, a healthcheck that came back after one didn't, or a ping tick that caught a silent loss. An online event whose healthcheck fails changes nothing, so nothing is delivered.

Details worth knowing:

  • A listener that throws is contained — the error is logged and the remaining listeners still run. One broken subscriber must not take the network state machine down with it.
  • Subscribing and unsubscribing from inside a listener is safe. A listener added during a dispatch first hears the next change; one removed during a dispatch is not called in that round.
  • destroy() drops every subscription without a final call: the state it resets to is a teardown, not an observation of the network. Re-subscribe after re-init().
// React
useEffect(() => NetworkConnection.subscribe(setOnline), []);

Healthcheck semantics

  • Any delivered response counts as online — even a 404 or 500. A 500 still travelled through DNS and TCP; the network demonstrably works. This measures reachability, not server health. Only a rejected request (DNS failure, connection refused, timeout) means offline.
  • The URL doesn't need a backend endpoint. Because any delivered response counts, a tiny static file shipped with your build works just as well — /health.txt, /favicon.ico, anything your hosting serves: NetworkConnection.init("/health.txt"). No server code, no extra route.
  • The request is sent with cache: "no-store" and a Cache-Control: no-cache header, so neither the browser's HTTP cache, a PWA's service worker cache, nor an intermediary proxy can answer on the network's behalf and fake a success while offline.
  • A check that hangs is aborted after healthcheckTimeout and counts as failed.
  • Concurrent triggers (the online event, a ping tick, a retry) share one in-flight request. A check that was in flight when an offline event arrived is discarded — the event is newer information.

restartIfNotFinishedWhenOnline

A wrapper for work that has to survive a connection drop. It checks that the network is really there before starting fn — and if it isn't, it just waits for the connection to come back and starts then.

If fn fails, it checks the network again. Gone → wait for the reconnect and run fn from scratch, as many times as it takes. Network fine → the failure was real (a validation error, a bug, a 500 your code threw on), so the original error is thrown to you and nothing is retried. Retries are unbounded by design — the promise stays pending across any number of offline periods.

Two details worth knowing: the check before fn is one healthcheck request per call (calls made at the same time share a single one), and destroy() while a call is waiting for the network rejects it with the destroy error instead of retrying.

A retry restarts fn from the beginning, so make the work safe to repeat: idempotent endpoints, an idempotency key, or a resumable protocol.

Silent losses & pingInterval

The offline event covers the loud failures. It says nothing about a network that is connected but dead — airport Wi-Fi behind a captive portal, a router whose uplink dropped. Pass pingInterval and, while online, a healthcheck runs every N ms; a failed ping flips the state to offline and stops the loop until the connection verifiably returns. The loop is a chained setTimeout — a check slower than the interval never stacks requests behind itself. No pingInterval, no background traffic.

SSR

init() works without a window — the event subscriptions are skipped, the healthcheck runs through the global fetch (Node ≥ 18), timers and promise helpers behave identically. With no fetch at all every check reports offline — the library degrades to a pessimist rather than crashing.

TypeScript

const n = await NetworkConnection.restartIfNotFinishedWhenOnline(async () => 42);
// n: number — T flows through the retries

NetworkConnection.init("/health", { pingInterval: "30s" }); // ✗ string is not a number
new NetworkConnection();                 // ✗ constructor is private (and throws at runtime)

License

MIT