Can you hear me now?
In the web platform, simple tasks are often anything but. Properly detecting whether the user is online/offline has been one of the “Surprisingly hard problems in computing” since, well, forever.
Web developers often ask one question (“Is this browser online?”) but when you dig into it, they’re really trying to ask a question that’s both simpler and much more complex: (“Can I reliably send and receive data from a target server?”).
The browser purports to offer an API which will answer this question via the simple navigator.online
property.
Unfortunately, this simple property doesn’t really answer the real question, because:
- The property is a snapshot of a moment in time (“Time of check vs. Time of use”) — network access could be lost an instant after you query the property.
- The property doesn’t indicate whether a request might be blocked by some other feature (firewall, proxy, security software, extension, etc).
- Not all features on all platforms participate (e.g. Airplane mode).
- The property indicates that the client has some form of connectivity, not necessarily connectivity to the desired site.
- The API can return what reasonable people would call a “False Positive”: The navigator.onLine documentation notes:
I encounter this issue all the time because I have HyperV installed:
The Network Information API has similar shortcomings. Non-browser Windows software can use the NLM API to try to learn about the user’s network availability, but it suffers from most of the same problems noted above. However, APIs like INetworkListManager_get_IsConnectedToInternet
have the same problems when the user is behind a Captive Portal or a target requires a VPN, or when the user is connected via Wifi to a router (“Yay! You’re online!”) that’s plugged into a cable modem that is turned off (“But you can’t get anywhere!”).
What To Do?
While it’s unfortunate that answering the simple question (“Is the user online?“) is complex/impossible, answering the real question has a straightforward solution: If you want to know if something will work, try it!
The approach taken by most products is simple.
When your code wants to know “Can I exchange data with foo.com”, you just send a network request “Hey, Foo.com, can you hear me?” (sometimes using a quick HEAD
request to a simple echo service) and you wait to hear back “Yup!”
If you don’t receive an affirmative response within a short timeout, you can conclude “Whelp, whether I’m connected or not, I can’t talk to the site I care about.”
You might then set up a retry loop, using a capped backoff delay[1] to avoid wasting a lot of effort.
-Eric
[1] For example, Chromium’s network error page retries as follows:
base::TimeDelta GetAutoReloadTime(size_t reload_count) {
static const int kDelaysMs[] = {0, 5_000, 30_000, 60_000,
300_000, 600_000, 1_800_000};
Chromium elsewhere contains a few notes on available approaches:
// (1) Use InternetGetConnectedState (wininet.dll). This function is really easy
// to use (literally a one-liner), and runs quickly. The drawback is it adds a
// dependency on the wininet DLL.
//
// (2) Enumerate all of the network interfaces using GetAdaptersAddresses
// (iphlpapi.dll), and assume we are "online" if there is at least one interface
// that is connected, and that interface is not a loopback or tunnel.
//
// Safari on Windows has a fairly simple implementation that does this:
// http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp.
//
// Mozilla similarly uses this approach:
// http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp
//
// The biggest drawback to this approach is it is quite complicated.
// WebKit's implementation for example doesn't seem to test for ICS gateways
// (internet connection sharing), whereas Mozilla's implementation has extra
// code to guess that.
//
// (3) The method used in this file comes from google talk, and is similar to
// method (2). The main difference is it enumerates the winsock namespace
// providers rather than the actual adapters.
//
// I ran some benchmarks comparing the performance of each on my Windows 7
// workstation. Here is what I found:
// * Approach (1) was pretty much zero-cost after the initial call.
// * Approach (2) took an average of 3.25 milliseconds to enumerate the
// adapters.
// * Approach (3) took an average of 0.8 ms to enumerate the providers.
//
// In terms of correctness, all three approaches were comparable for the simple
// experiments I ran... However none of them correctly returned "offline" when
// executing 'ipconfig /release'.
Impatient optimist. Dad. Author/speaker. Created Fiddler & SlickRun. PM @ Microsoft 2001-2012, and 2018-2022, working on Office, IE, and Edge. Now a SWE on Microsoft Defender Web Protection. My words are my own, I do not speak for any other entity. View more posts