Is your app online? Here's how to reliably know in just 10 lines of JS [Guide]
Tags: tutorial, javascript, webdev, todayilearned
Published: 2020-09-06 12:34:21

We usually expect our web apps to be online but that ignores reality.

People go on planes, enter tunnels, have bad internet, or just decide to go offline. Depending on your user's expectations, should your app stop working?

If not, you'll need a reliable way to detect if your app is offline in order to offer the proper experience.

Here's how in just 10 lines of JS.

TL;DR Code is at the bottom for your copy/pasting pleasure!

## Browser Navigator

Before coding, let's look at the lay of the land.

Browsers come with the `navigator.onLine` property. This straight up returns `true` or `false` based on the browser state.

```js
function isOnline () {
    return window.navigator.onLine
}
```

So are we done? Well, because of how it works, you can only trust `false` to mean offline. `true` could be more varied.

[MDN - Navigator.onLine](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine)

## So how to tell if you also have access to the internet?

Because of the way navigator works, we know when we're offline but online is a little murky.

Navigator returns `true` when the device is connected to a network but that doesn't mean you are also connected to the internet which are 2 very different things.

Your first instinct might be to make a request to some random site and seeing if you get a success or an error.

But what kind of request? And to which resource? 🤔

### Sending the perfect request ✨

Checking the network status might happen often so ideally our request response should be as small as possible. This will make it faster and it will consume less bandwidth.

To figure what kind of requests are available, we can look at the different HTTP methods and the HEAD method stands out as the best (TRACE might actually be better but isn't supported by fetch).

A HEAD request is almost exactly like a GET request except we get no response data, only the HEADers. This works out great since our goal is to check if the request was successful or not, we don't actually care about any data returned.

### Where should you send the request?

We have the perfect request but where should it go?

Your first instinct might be to send it to some service or site that is always active. Maybe google.com? But try that and you will be greeted by CORS errors.

This makes sense, Google (and every other site by default) won't accept requests from random sites.
The next option is to make your own server or cloud function that would accept requests exclusively from your application!

But that's far too much work for a simple network check and a good developer is a lazy developer.

So back to square one, CORS errors.

Their goal is prevent security issues on requests coming from a different origin. Then wouldn't it be possible send the request to your own origin?

The answer is yes! And you can automatically get your origin with `window.location.origin`.

```js
async function isOnline () {
  if (!window.navigator.onLine) return false

  const response = await fetch(
    window.location.origin,
    { method: 'HEAD' },
  )

  return response.ok
}
```

Now you can ping your own site and wait for a response, but the problem is since we always send the same request to the same URL, your browser will waste no time caching the result making our function useless.

So the final trick is to send our request with a randomized query parameter!
This will have no impact on the result and will prevent your browser from caching the response since it goes to a different URL each time.

And thanks to the built-in URL class, we don't even need to manually manipulate strings.

Here is the final code along with some extra error handling.

```js
getRandomString () {
  return Math.random().toString(36).substring(2, 15)
}

async function isOnline () {
  if (!window.navigator.onLine) return false

  // avoid CORS errors with a request to your own origin
  const url = new URL(window.location.origin)

  // random value to prevent cached responses
  url.searchParams.set('rand', getRandomString())

  try {
    const response = await fetch(
      url.toString(),
      { method: 'HEAD' },
    )

    return response.ok
  } catch {
    return false
  }
}
```

This gives us a more reliable check on the network's status but it is missing some configuration options.

Notably we always check with the same URL. This could be fine but what if you would prefer to ping your own server or just something closer to reduce latency?

Additionally this runs only on call, it might be useful to be able to pass a callback, or have some kind of observer.

You do get event listeners when the network status changes...

```js
window.addEventListener('online', () => console.log('online'))
window.addEventListener('offline', () => console.log('offline'))
```

The final result here is very simple and I leave it up to you to expand this to fit your needs!

---

Thanks for reading this article! Let me know what you think in a comment or message me directly on twitter [@MaxMonteil](https://twitter.com/maxmonteil)