Combine approaches for easy, lightweight JavaScript user information detection

Basic browser/user detection can be important for providing an enhanced user experience. For example, making a best-guess detection of the user’s IP address (subject to the usual caveats about VPN use) can help in pre-selecting a user’s country of origin, enabling faster form fill-outs. The user’s preferred language, especially on first access of a site, is obviously of huge importance; detecting it early with a non-intrusive script can help avoid clunkier approaches such as passing out language-differentiated URLs to globalized versions of your site, which may be passed between users with unintended consequences.

Especially for a site which already makes use of JavaScript, an unobtrusive script can make a difference. And while there are fairly robust libraries such as platform.js, they often do not address all concerns of detecting language, geoIP, and browser. Adding extra dependencies to a site, in terms of maintenance burden and JS payload, is desirable to avoid when possible.

It’s easy enough to create a tiny script which merges freely available geoIP with basic browser/language detection, as shown below. There are so many free geoIP web services available these days, a first implementation can simply rely on one, perhaps adding a fallback as necessary. The excellent freegeoip.net, shown in the sample below, at time of this writing has shown high reliability, allows for up to 15,000 requests per hour from the same end-user IP address, and can also be installed for free in any Linux environment.

The simple demo code, with browser and OS versions not yet filled in, appears below, and may be run at JSFiddle . This could, for example, be passed as a JSON bundle in a call to a web service, or submitted with a page. While the code may be reused freely, as it essentially cobbles together multiple simple well-known methods into an easy-to-use package, the emphasis is on the approach: a lightweight script that avoids heavy dependencies and still captures useful information about a user. As always, use such information responsibly, especially keeping in mind GDPR regulations which will have taken full effect by May 2018.

function UserInfo() {
  var self = this;
  this.language = ((navigator.languages ? navigator.languages[0] : (navigator.language || navigator.userLanguage)) + '').substring(0, 2);
  this.browserName = '';
  this.browserVersion = '';
  this.osName = '';
  this.osVersion = '';
  this.country = '';
  this.ipAddress = '';
  this.city = '';
  this.region = '';
  this.postalCode = '';
  
  if (navigator && navigator.userAgent) {
    var ua = (navigator.userAgent + '').toLowerCase();
    var platform = ua;
    
    var i = platform.indexOf('(');
    if (i > -1)
      platform = platform.substring(i + 1);

    i = platform.indexOf(')');
    if (i > -1)
      platform = platform.substring(0, i);
      
    if (platform.indexOf('windows') > -1)
      this.osName = 'Windows';
    else if (platform.indexOf('android') > -1)
      this.osName = 'Android';
    else if (platform.indexOf('macintosh') > -1 || platform.indexOf('macos') > -1)
      this.osName = 'macOS';
    else if (platform.indexOf('iphone') > -1 || platform.indexOf('ipad') > -1)
      this.osName = 'iOS';
  }

  if (!!window.chrome && !!window.chrome.webstore)
    this.browserName = 'Chrome';
  else if (typeof InstallTrigger !== 'undefined')
    this.browserName = 'Firefox';
  else if (/*@cc_on!@*/false || !!document.documentMode)
    this.browserName = 'IE';
  else if (/constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification))
    this.browserName = 'Safari';
  else if ((!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0)
    this.browserName = 'Opera';
  else if (!!window.StyleMedia)
    this.browserName = 'Edge';
  
try {
    $.getJSON('//freegeoip.net/json/?callback=?', function(data) {
      self.ipAddress = data.ip + '';
      self.city = data.city + '';
      self.region = data.region_code + '';
      self.country = data.country_code + '';      
      self.postalCode = data.zip_code + '';
    });
} catch (err) {}

  this.ids = []; 
  
  this.addID = function(sn, st, idt, i) {
    var newID = {};
    newID.systemName = sn + '';
    newID.systemType = st + '';
    newID.idType = idt + '';
    newID.id = i + '';      
    this.ids.push(newID);
  };  
}

The minified version is relatively compact, at 1560 characters:

function UserInfo(){var i=this;if(this.language=((navigator.languages?navigator.languages[0]:navigator.language||navigator.userLanguage)+"").substring(0,2),this.browserName="",this.browserVersion="",this.osName="",this.osVersion="",this.country="",this.ipAddress="",this.city="",this.region="",this.postalCode="",navigator&&navigator.userAgent){var e=(navigator.userAgent+"").toLowerCase(),o=e.indexOf("(");o>-1&&(e=e.substring(o+1)),(o=e.indexOf(")"))>-1&&(e=e.substring(0,o)),e.indexOf("windows")>-1?this.osName="Windows":e.indexOf("anddroid")>-1?this.osName="Android":e.indexOf("macintosh")>-1||e.indexOf("macos")>-1?this.osName="macOS":(e.indexOf("iphone")>-1||e.indexOf("ipad")>-1)&&(this.osName="iOS")}window.chrome&&window.chrome.webstore?this.browserName="Chrome":"undefined"!=typeof InstallTrigger?this.browserName="Firefox":document.documentMode?this.browserName="IE":/constructor/i.test(window.HTMLElement)||"[object SafariRemoteNotification]"===(!window.safari||safari.pushNotification).toString()?this.browserName="Safari":window.opr&&opr.addons||window.opera||navigator.userAgent.indexOf(" OPR/")>=0?this.browserName="Opera":window.StyleMedia?this.browserName="Edge":(isChrome||isOpera)&&window.CSS&&(this.browserName="Blink");try{$.getJSON("//freegeoip.net/json/?callback=?",function(e){i.ipAddress=e.ip+"",i.city=e.city+"",i.region=e.region_code+"",i.country=e.country_code+"",i.postalCode=e.zip_code+""})}catch(i){}this.ids=[],this.addID=function(i,e,o,s){var t={};t.systemName=i+"",t.systemType=e+"",t.idType=o+"",t.id=s+"",this.ids.push(t)}}

Advertisements