| '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; |