Detecting Outdated Browsers in Vanilla JavaScript

Recently I had to build a simple banner in a React app that informed users if they use an outdated browser, such as Internet Explorer 11. Initially, I thought that this task would be rather simple, but surprisingly it took me quite a bit to figure out the best solution (for this case). Let’s have a look at how I solved it.

Requirements

The Investigation

Upon my investigation, I encountered https://browser-update.org/. They provide a lightweight JavaScript library and add an already styled banner automatically to your website (although you can customize it). However, I needed to integrate said banner into a React component and figured that it would, therefore, be easier to not use this library.

I also found some other third-party scripts doing more or less the same kind of work, but in the end, I decided that I didn’t want to include any more external JavaScript in the app.

While casually looking through StackOverflow for some ideas, I stumbled upon this thread. The accepted answer from Dhanil looked promising to me, so I decided to give it a try and (literally) wrap it up.

The Solution

Again, the below snippet is not mine, it’s from a guy called Dhanil on StackOverflow. Credit to him for providing this handy piece of code.

var browser = (function() {
  var ua = navigator.userAgent,
    tem,
    M =
      ua.match(
        /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i
      ) || [];

  if (/trident/i.test(M[1])) {
    tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
    return { name: "IE", version: tem[1] || "" };
  }

  if (M[1] === "Chrome") {
    tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
    if (tem != null) {
      return { name: tem[1].replace("OPR", "Opera"), version: tem[2] };
    }
  }

  M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];

  if ((tem = ua.match(/version\/(\d+)/i)) != null) {
    M.splice(1, 1, tem[1]);
  }

  return { name: M[0], version: M[1] };
})();

The above script basically gets the user agent string and applies some tests using regular expressions to figure out the name of the browser and its version. If you have ever worked with user agents, you might know what kind of pain you’re dealing with, so it surprised me quite a bit how well this script works.

However, I decided that I wanted a global class that would provide me with a convenient API, like isSupported() or isIE(). Here’s the result:

/**
 * BrowserDetector
 * 
 * This util checks the current browser name and version and offers a
 * convinient API to test for specific versions or browsers and whether
 * the current visitor uses a supported browser or not.
 */
export default class BrowserDetector {
  constructor() {
    this.browser = {};
    this.unsupportedBrowsers = {
      Chrome: 70,
      Firefox: 60,
      IE: 10,
      Edge: 15,
      Opera: 50,
      Safari: 12
    };

    this._detectBrowser();
  }

  /**
   * Detects the current browser and its version number.
   * 
   * @returns {Object} An object with keys for browser `name` and `version`.
   */
  _detectBrowser() {
    this.browser = (function() {
      var ua = navigator.userAgent,
        tem,
        M =
          ua.match(
            /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i
          ) || [];

      if (/trident/i.test(M[1])) {
        tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
        return { name: "IE", version: tem[1] || "" };
      }

      if (M[1] === "Chrome") {
        tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
        if (tem != null) {
          return { name: tem[1].replace("OPR", "Opera"), version: tem[2] };
        }
      }

      M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];

      if ((tem = ua.match(/version\/(\d+)/i)) != null) {
        M.splice(1, 1, tem[1]);
      }

      return { name: M[0], version: M[1] };
    })();
  }

  /**
   * Checks if the current browser is Internet Explorer.
   * 
   * @returns {Boolean}
   */
  get isIE() {
    return this.browser.name === 'IE';
  }

  /**
   * Checks if the current browser is Edge.
   * 
   * @returns {Boolean}
   */
  get isEdge() {
    return this.browser.name === 'Edge';
  }

  /**
   * Checks if the current browser is from Microsoft (Edge
   * or Internet Explorer).
   * 
   * @returns {Boolean}
   */
  get isMicrosoft() {
    return this.isIE || this.isEdge;
  }

  /**
   * Checks if the current browser is Firefox.
   * 
   * @returns {Boolean}
   */
  get isFirefox() {
    return this.browser.name === 'Firefox';
  }

  /**
   * Checks if the current browser is Chrome.
   * 
   * @returns {Boolean}
   */
  get isChrome() {
    return this.browser.name === 'Chrome';
  }

  /**
   * Checks if the current browser is Safari.
   * 
   * @returns {Boolean}
   */
  get isSafari() {
    return this.browser.name === 'Safari';
  }

  /**
   * Checks if the current browser is from an Android device.
   * 
   * @returns {Boolean}
   */
  get isAndroid() {
    return /Android/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is from a BlackBerry device.
   * 
   * @returns {Boolean}
   */
  get isBlackBerry() {
    return /BlackBerry/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is from a Windows Mobile device.
   * 
   * @returns {Boolean}
   */
  get isWindowsMobile() {
    return /IEMobile/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is Mobile Safari.
   * 
   * @returns {Boolean}
   */
  get isIOS() {
    return /iPhone|iPad|iPod/i.test(navigator.userAgent);
  }

  /**
   * Checks if the current browser is a mobile browser.
   * 
   * @returns {Boolean}
   */
  get isMobile() {
    return (
      this.isAndroid || this.isBlackBerry || this.isWindowsMobile || this.isIOS
    );
  }

  /**
   * Checks if the current browser is supported by
   * our environment.
   * 
   * @returns {Boolean}
   */
  isSupported() {
    if (this.unsupportedBrowsers.hasOwnProperty(this.browser.name)) {
      if (+this.browser.version > this.unsupportedBrowsers[this.browser.name]) {
        return true;
      }
    }

    return false;
  }
}
if ((new BrowserDetector).isIE) {
  toggleOutdatedBrowserMessage(); // Or whatever...
}

If you want to see an actual example of how you could use the above script in a React app, you might want to have a look at this snippet:

import { Component } from 'react;
import { render } from 'react-dom';

import BrowserDetector from './browser-detector.js';

function TopBanner(props) {
  if (!props.open)
    return null;  
  }
  
  return (
    <div role="alert">
      <strong>Outdated browser detected!</strong>
      Consider upgrading or switching to a <a href="http://outdatedbrowser.com/en">different one</a>.
    </div>
  );
}

class App extends Component {
  state = {
    outdatedBrowser: false
  }
  
  componentDidMount() {
    if ((new BrowserDetector).isSupported()) {
      this.setState({
        outdatedBrowser: true
      });
    }
  }

  render() {
    return (
      <>
        <TopBanner open={this.state.outdatedBrowser} />
        <h1>Hello World!</h1>
      </>
     );
  }
}
render(<App />, document.getElementById('root'));

You can see one functional component called TopBanner and the main class component called App. App includes the TopBanner and gives it a prop called open, which can either be true or false. In the App component, we make use of a lifecycle method called componentDidMount , which is called each time the component is mounted into the DOM. If that’s the case, it will run our little browser detector script and set the local state, therefore toggling the TopBanner

Inside the TopBanner component I’ve just put a single conditional that checks whether open is set to true or false. In the second case, nothing gets rendered into the DOM, as if the component doesn’t exist. Otherwise, we display the message which could be extended by a close button and some styling.

Conclusion

With the above code, it’s pretty easy and reliable to detect browsers and their versions. You might want to use an already existing library (like this one), but I for myself prefer to not add more dependencies than absolutely necessary.

Final note: Please don’t make the same error as other, (major) web applications do and lock out users based on what kind of browser they use. Use feature detection rather than browser detection if possible, because locking out all Edge users just because Edge doesn’t support one API you use is not a good move. If a certain browser is missing a feature you are using in your app, consider just deactivating the said feature, but leave the rest of the site accessible.

In my case, I don’t block any user because of their browser but just display a simple, informative message to them. If a user visits the app with IE9, well, he won’t get far anyway.

Back