--- /dev/null
+'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;
+
+/**
+ * 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) 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;