| 'use strict'; | 
 |  | 
 | /** | 
 |  * This is where all the magic comes from, specially crafted for `useragent`. | 
 |  */ | 
 | var regexps = require('./lib/regexps'); | 
 |  | 
 | /** | 
 |  * Reduce references by storing the lookups. | 
 |  */ | 
 | // OperatingSystem parsers: | 
 | var osparsers = regexps.os | 
 |   , osparserslength = osparsers.length; | 
 |  | 
 | // UserAgent parsers: | 
 | var agentparsers = regexps.browser | 
 |   , agentparserslength = agentparsers.length; | 
 |  | 
 | // Device parsers: | 
 | var deviceparsers = regexps.device | 
 |   , deviceparserslength = deviceparsers.length; | 
 |  | 
 | /** | 
 |  * The representation of a parsed user agent. | 
 |  * | 
 |  * @constructor | 
 |  * @param {String} family The name of the browser | 
 |  * @param {String} major Major version of the browser | 
 |  * @param {String} minor Minor version of the browser | 
 |  * @param {String} patch Patch version of the browser | 
 |  * @param {String} source The actual user agent string | 
 |  * @api public | 
 |  */ | 
 | function Agent(family, major, minor, patch, source) { | 
 |   this.family = family || 'Other'; | 
 |   this.major = major || '0'; | 
 |   this.minor = minor || '0'; | 
 |   this.patch = patch || '0'; | 
 |   this.source = source || ''; | 
 | } | 
 |  | 
 | /** | 
 |  * OnDemand parsing of the Operating System. | 
 |  * | 
 |  * @type {OperatingSystem} | 
 |  * @api public | 
 |  */ | 
 | Object.defineProperty(Agent.prototype, 'os', { | 
 |   get: function lazyparse() { | 
 |     var userAgent = this.source | 
 |       , length = osparserslength | 
 |       , parsers = osparsers | 
 |       , i = 0 | 
 |       , parser | 
 |       , res; | 
 |  | 
 |     for (; i < length; i++) { | 
 |       if (res = parsers[i][0].exec(userAgent)) { | 
 |         parser = parsers[i]; | 
 |  | 
 |         if (parser[1]) res[1] = parser[1].replace('$1', res[1]); | 
 |         break; | 
 |       } | 
 |     } | 
 |  | 
 |     return Object.defineProperty(this, 'os', { | 
 |         value: !parser || !res | 
 |           ? new OperatingSystem() | 
 |           : new OperatingSystem( | 
 |                 res[1] | 
 |               , parser[2] || res[2] | 
 |               , parser[3] || res[3] | 
 |               , parser[4] || res[4] | 
 |             ) | 
 |     }).os; | 
 |   }, | 
 |  | 
 |   /** | 
 |    * Bypass the OnDemand parsing and set an OperatingSystem instance. | 
 |    * | 
 |    * @param {OperatingSystem} os | 
 |    * @api public | 
 |    */ | 
 |   set: function set(os) { | 
 |     if (!(os instanceof OperatingSystem)) return false; | 
 |  | 
 |     return Object.defineProperty(this, 'os', { | 
 |       value: os | 
 |     }).os; | 
 |   } | 
 | }); | 
 |  | 
 | /** | 
 |  * OnDemand parsing of the Device type. | 
 |  * | 
 |  * @type {Device} | 
 |  * @api public | 
 |  */ | 
 | Object.defineProperty(Agent.prototype, 'device', { | 
 |   get: function lazyparse() { | 
 |     var userAgent = this.source | 
 |       , length = deviceparserslength | 
 |       , parsers = deviceparsers | 
 |       , i = 0 | 
 |       , parser | 
 |       , res; | 
 |  | 
 |     for (; i < length; i++) { | 
 |       if (res = parsers[i][0].exec(userAgent)) { | 
 |         parser = parsers[i]; | 
 |  | 
 |         if (parser[1]) res[1] = parser[1].replace('$1', res[1]); | 
 |         break; | 
 |       } | 
 |     } | 
 |  | 
 |     return Object.defineProperty(this, 'device', { | 
 |         value: !parser || !res | 
 |           ? new Device() | 
 |           : new Device( | 
 |                 res[1] | 
 |               , parser[2] || res[2] | 
 |               , parser[3] || res[3] | 
 |               , parser[4] || res[4] | 
 |             ) | 
 |     }).device; | 
 |   }, | 
 |  | 
 |   /** | 
 |    * Bypass the OnDemand parsing and set an Device instance. | 
 |    * | 
 |    * @param {Device} device | 
 |    * @api public | 
 |    */ | 
 |   set: function set(device) { | 
 |     if (!(device instanceof Device)) return false; | 
 |  | 
 |     return Object.defineProperty(this, 'device', { | 
 |       value: device | 
 |     }).device; | 
 |   } | 
 | }); | 
 | /*** Generates a string output of the parsed user agent. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | Agent.prototype.toAgent = function toAgent() { | 
 |   var output = this.family | 
 |     , version = this.toVersion(); | 
 |  | 
 |   if (version) output += ' '+ version; | 
 |   return output; | 
 | }; | 
 |  | 
 | /** | 
 |  * Generates a string output of the parser user agent and operating system. | 
 |  * | 
 |  * @returns {String}  "UserAgent 0.0.0 / OS" | 
 |  * @api public | 
 |  */ | 
 | Agent.prototype.toString = function toString() { | 
 |   var agent = this.toAgent() | 
 |     , os = this.os !== 'Other' ? this.os : false; | 
 |  | 
 |   return agent + (os ? ' / ' + os : ''); | 
 | }; | 
 |  | 
 | /** | 
 |  * Outputs a compiled veersion number of the user agent. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | Agent.prototype.toVersion = function toVersion() { | 
 |   var version = ''; | 
 |  | 
 |   if (this.major) { | 
 |     version += this.major; | 
 |  | 
 |     if (this.minor) { | 
 |      version += '.' + this.minor; | 
 |  | 
 |      // Special case here, the patch can also be Alpha, Beta etc so we need | 
 |      // to check if it's a string or not. | 
 |      if (this.patch) { | 
 |       version += (isNaN(+this.patch) ? ' ' : '.') + this.patch; | 
 |      } | 
 |     } | 
 |   } | 
 |  | 
 |   return version; | 
 | }; | 
 |  | 
 | /** | 
 |  * Outputs a JSON string of the Agent. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | Agent.prototype.toJSON = function toJSON() { | 
 |   return { | 
 |       family: this.family | 
 |     , major: this.major | 
 |     , minor: this.minor | 
 |     , patch: this.patch | 
 |     , device: this.device | 
 |     , os: this.os | 
 |   }; | 
 | }; | 
 |  | 
 | /** | 
 |  * The representation of a parsed Operating System. | 
 |  * | 
 |  * @constructor | 
 |  * @param {String} family The name of the os | 
 |  * @param {String} major Major version of the os | 
 |  * @param {String} minor Minor version of the os | 
 |  * @param {String} patch Patch version of the os | 
 |  * @api public | 
 |  */ | 
 | function OperatingSystem(family, major, minor, patch) { | 
 |   this.family = family || 'Other'; | 
 |   this.major = major || '0'; | 
 |   this.minor = minor || '0'; | 
 |   this.patch = patch || '0'; | 
 | } | 
 |  | 
 | /** | 
 |  * Generates a stringified version of the Operating System. | 
 |  * | 
 |  * @returns {String} "Operating System 0.0.0" | 
 |  * @api public | 
 |  */ | 
 | OperatingSystem.prototype.toString = function toString() { | 
 |   var output = this.family | 
 |     , version = this.toVersion(); | 
 |  | 
 |   if (version) output += ' '+ version; | 
 |   return output; | 
 | }; | 
 |  | 
 | /** | 
 |  * Generates the version of the Operating System. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | OperatingSystem.prototype.toVersion = function toVersion() { | 
 |   var version = ''; | 
 |  | 
 |   if (this.major) { | 
 |     version += this.major; | 
 |  | 
 |     if (this.minor) { | 
 |      version += '.' + this.minor; | 
 |  | 
 |      // Special case here, the patch can also be Alpha, Beta etc so we need | 
 |      // to check if it's a string or not. | 
 |      if (this.patch) { | 
 |       version += (isNaN(+this.patch) ? ' ' : '.') + this.patch; | 
 |      } | 
 |     } | 
 |   } | 
 |  | 
 |   return version; | 
 | }; | 
 |  | 
 | /** | 
 |  * Outputs a JSON string of the OS, values are defaulted to undefined so they | 
 |  * are not outputed in the stringify. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | OperatingSystem.prototype.toJSON = function toJSON(){ | 
 |   return { | 
 |       family: this.family | 
 |     , major: this.major || undefined | 
 |     , minor: this.minor || undefined | 
 |     , patch: this.patch || undefined | 
 |   }; | 
 | }; | 
 |  | 
 | /** | 
 |  * The representation of a parsed Device. | 
 |  * | 
 |  * @constructor | 
 |  * @param {String} family The name of the device | 
 |  * @param {String} major Major version of the device | 
 |  * @param {String} minor Minor version of the device | 
 |  * @param {String} patch Patch version of the device | 
 |  * @api public | 
 |  */ | 
 | function Device(family, major, minor, patch) { | 
 |   this.family = family || 'Other'; | 
 |   this.major = major || '0'; | 
 |   this.minor = minor || '0'; | 
 |   this.patch = patch || '0'; | 
 | } | 
 |  | 
 | /** | 
 |  * Generates a stringified version of the Device. | 
 |  * | 
 |  * @returns {String} "Device 0.0.0" | 
 |  * @api public | 
 |  */ | 
 | Device.prototype.toString = function toString() { | 
 |   var output = this.family | 
 |     , version = this.toVersion(); | 
 |  | 
 |   if (version) output += ' '+ version; | 
 |   return output; | 
 | }; | 
 |  | 
 | /** | 
 |  * Generates the version of the Device. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | Device.prototype.toVersion = function toVersion() { | 
 |   var version = ''; | 
 |  | 
 |   if (this.major) { | 
 |     version += this.major; | 
 |  | 
 |     if (this.minor) { | 
 |      version += '.' + this.minor; | 
 |  | 
 |      // Special case here, the patch can also be Alpha, Beta etc so we need | 
 |      // to check if it's a string or not. | 
 |      if (this.patch) { | 
 |       version += (isNaN(+this.patch) ? ' ' : '.') + this.patch; | 
 |      } | 
 |     } | 
 |   } | 
 |  | 
 |   return version; | 
 | }; | 
 |  | 
 | /** | 
 |  * Outputs a JSON string of the Device, values are defaulted to undefined so they | 
 |  * are not outputed in the stringify. | 
 |  * | 
 |  * @returns {String} | 
 |  * @api public | 
 |  */ | 
 | Device.prototype.toJSON = function toJSON() { | 
 |   return { | 
 |       family: this.family | 
 |     , major: this.major || undefined | 
 |     , minor: this.minor || undefined | 
 |     , patch: this.patch || undefined | 
 |   }; | 
 | }; | 
 |  | 
 | /** | 
 |  * Small nifty thick that allows us to download a fresh set regexs from t3h | 
 |  * Int3rNetz when we want to. We will be using the compiled version by default | 
 |  * but users can opt-in for updates. | 
 |  * | 
 |  * @param {Boolean} refresh Refresh the dataset from the remote | 
 |  * @api public | 
 |  */ | 
 | module.exports = function updater() { | 
 |   try { | 
 |     require('./lib/update').update(function updating(err, results) { | 
 |       if (err) { | 
 |         console.log('[useragent] Failed to update the parsed due to an error:'); | 
 |         console.log('[useragent] '+ (err.message ? err.message : err)); | 
 |         return; | 
 |       } | 
 |  | 
 |       regexps = results; | 
 |  | 
 |       // OperatingSystem parsers: | 
 |       osparsers = regexps.os; | 
 |       osparserslength = osparsers.length; | 
 |  | 
 |       // UserAgent parsers: | 
 |       agentparsers = regexps.browser; | 
 |       agentparserslength = agentparsers.length; | 
 |  | 
 |       // Device parsers: | 
 |       deviceparsers = regexps.device; | 
 |       deviceparserslength = deviceparsers.length; | 
 |     }); | 
 |   } catch (e) { | 
 |     console.error('[useragent] If you want to use automatic updating, please add:'); | 
 |     console.error('[useragent]   - request (npm install request --save)'); | 
 |     console.error('[useragent]   - yamlparser (npm install yamlparser --save)'); | 
 |     console.error('[useragent] To your own package.json'); | 
 |   } | 
 | }; | 
 |  | 
 | // Override the exports with our newly set module.exports | 
 | exports = module.exports; | 
 |  | 
 | /** | 
 |  * Nao that we have setup all the different classes and configured it we can | 
 |  * actually start assembling and exposing everything. | 
 |  */ | 
 | exports.Device = Device; | 
 | exports.OperatingSystem = OperatingSystem; | 
 | exports.Agent = Agent; | 
 |  | 
 | /** | 
 |  * Check if the userAgent is something we want to parse with regexp's. | 
 |  * | 
 |  * @param {String} userAgent The userAgent. | 
 |  * @returns {Boolean} | 
 |  */ | 
 | function isSafe(userAgent) { | 
 |   var consecutive = 0 | 
 |     , code = 0; | 
 |  | 
 |   for (var i = 0; i < userAgent.length; i++) { | 
 |     code = userAgent.charCodeAt(i); | 
 |     // numbers between 0 and 9, letters between a and z | 
 |     if ((code >= 48 && code <= 57) || (code >= 97 && code <= 122)) { | 
 |       consecutive++; | 
 |     } else { | 
 |       consecutive = 0; | 
 |     } | 
 |  | 
 |     if (consecutive >= 100) { | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   return true | 
 | } | 
 |  | 
 |  | 
 | /** | 
 |  * Parses the user agent string with the generated parsers from the | 
 |  * ua-parser project on google code. | 
 |  * | 
 |  * @param {String} userAgent The user agent string | 
 |  * @param {String} [jsAgent] Optional UA from js to detect chrome frame | 
 |  * @returns {Agent} | 
 |  * @api public | 
 |  */ | 
 | exports.parse = function parse(userAgent, jsAgent) { | 
 |   if (!userAgent || !isSafe(userAgent)) return new Agent(); | 
 |  | 
 |   var length = agentparserslength | 
 |     , parsers = agentparsers | 
 |     , i = 0 | 
 |     , parser | 
 |     , res; | 
 |  | 
 |   for (; i < length; i++) { | 
 |     if (res = parsers[i][0].exec(userAgent)) { | 
 |       parser = parsers[i]; | 
 |  | 
 |       if (parser[1]) res[1] = parser[1].replace('$1', res[1]); | 
 |       if (!jsAgent) return new Agent( | 
 |           res[1] | 
 |         , parser[2] || res[2] | 
 |         , parser[3] || res[3] | 
 |         , parser[4] || res[4] | 
 |         , userAgent | 
 |       ); | 
 |  | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   // Return early if we didn't find an match, but might still be able to parse | 
 |   // the os and device, so make sure we supply it with the source | 
 |   if (!parser || !res) return new Agent('', '', '', '', userAgent); | 
 |  | 
 |   // Detect Chrome Frame, but make sure it's enabled! So we need to check for | 
 |   // the Chrome/ so we know that it's actually using Chrome under the hood. | 
 |   if (jsAgent && ~jsAgent.indexOf('Chrome/') && ~userAgent.indexOf('chromeframe')) { | 
 |     res[1] = 'Chrome Frame (IE '+ res[1] +'.'+ res[2] +')'; | 
 |  | 
 |     // Run the JavaScripted userAgent string through the parser again so we can | 
 |     // update the version numbers; | 
 |     parser = parse(jsAgent); | 
 |     parser[2] = parser.major; | 
 |     parser[3] = parser.minor; | 
 |     parser[4] = parser.patch; | 
 |   } | 
 |  | 
 |   return new Agent( | 
 |       res[1] | 
 |     , parser[2] || res[2] | 
 |     , parser[3] || res[3] | 
 |     , parser[4] || res[4] | 
 |     , userAgent | 
 |   ); | 
 | }; | 
 |  | 
 | /** | 
 |  * If you are doing a lot of lookups you might want to cache the results of the | 
 |  * parsed user agent string instead, in memory. | 
 |  * | 
 |  * @TODO We probably want to create 2 dictionary's here 1 for the Agent | 
 |  * instances and one for the userAgent instance mapping so we can re-use simular | 
 |  * Agent instance and lower our memory consumption. | 
 |  * | 
 |  * @param {String} userAgent The user agent string | 
 |  * @param {String} jsAgent Optional UA from js to detect chrome frame | 
 |  * @api public | 
 |  */ | 
 | var LRU = require('lru-cache')(5000); | 
 | exports.lookup = function lookup(userAgent, jsAgent) { | 
 |   var key = (userAgent || '')+(jsAgent || '') | 
 |     , cached = LRU.get(key); | 
 |  | 
 |   if (cached) return cached; | 
 |   LRU.set(key, (cached = exports.parse(userAgent, jsAgent))); | 
 |  | 
 |   return cached; | 
 | }; | 
 |  | 
 | /** | 
 |  * Does a more inaccurate but more common check for useragents identification. | 
 |  * The version detection is from the jQuery.com library and is licensed under | 
 |  * MIT. | 
 |  * | 
 |  * @param {String} useragent The user agent | 
 |  * @returns {Object} matches | 
 |  * @api public | 
 |  */ | 
 | exports.is = function is(useragent) { | 
 |   var ua = (useragent || '').toLowerCase() | 
 |     , details = { | 
 |         chrome: false | 
 |       , firefox: false | 
 |       , ie: false | 
 |       , mobile_safari: false | 
 |       , mozilla: false | 
 |       , opera: false | 
 |       , safari: false | 
 |       , webkit: false | 
 |       , android: false | 
 |       , version: (ua.match(exports.is.versionRE) || [0, "0"])[1] | 
 |     }; | 
 |  | 
 |   if (~ua.indexOf('webkit')) { | 
 |     details.webkit = true; | 
 |  | 
 |     if (~ua.indexOf('android')){ | 
 |       details.android = true; | 
 |     } | 
 |  | 
 |     if (~ua.indexOf('chrome')) { | 
 |       details.chrome = true; | 
 |     } else if (~ua.indexOf('safari')) { | 
 |       details.safari = true; | 
 |  | 
 |       if (~ua.indexOf('mobile') && ~ua.indexOf('apple')) { | 
 |         details.mobile_safari = true; | 
 |       } | 
 |     } | 
 |   } else if (~ua.indexOf('opera')) { | 
 |     details.opera = true; | 
 |   } else if (~ua.indexOf('trident') || ~ua.indexOf('msie')) { | 
 |     details.ie = true; | 
 |   } else if (~ua.indexOf('mozilla') && !~ua.indexOf('compatible')) { | 
 |     details.mozilla = true; | 
 |  | 
 |     if (~ua.indexOf('firefox')) details.firefox = true; | 
 |   } | 
 |  | 
 |  | 
 |   return details; | 
 | }; | 
 |  | 
 | /** | 
 |  * Parses out the version numbers. | 
 |  * | 
 |  * @type {RegExp} | 
 |  * @api private | 
 |  */ | 
 | exports.is.versionRE = /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/; | 
 |  | 
 | /** | 
 |  * Transform a JSON object back to a valid userAgent string | 
 |  * | 
 |  * @param {Object} details | 
 |  * @returns {Agent} | 
 |  */ | 
 | exports.fromJSON = function fromJSON(details) { | 
 |   if (typeof details === 'string') details = JSON.parse(details); | 
 |  | 
 |   var agent = new Agent(details.family, details.major, details.minor, details.patch) | 
 |     , os = details.os; | 
 |  | 
 |   // The device family was added in v2.0 | 
 |   if ('device' in details) { | 
 |     agent.device = new Device(details.device.family); | 
 |   } else { | 
 |     agent.device = new Device(); | 
 |   } | 
 |  | 
 |   if ('os' in details && os) { | 
 |     // In v1.1.0 we only parsed out the Operating System name, not the full | 
 |     // version which we added in v2.0. To provide backwards compatible we should | 
 |     // we should set the details.os as family | 
 |     if (typeof os === 'string') { | 
 |       agent.os = new OperatingSystem(os); | 
 |     } else { | 
 |       agent.os = new OperatingSystem(os.family, os.major, os.minor, os.patch); | 
 |     } | 
 |   } | 
 |  | 
 |   return agent; | 
 | }; | 
 |  | 
 | /** | 
 |  * Library version. | 
 |  * | 
 |  * @type {String} | 
 |  * @api public | 
 |  */ | 
 | exports.version = require('./package.json').version; |