4 * This is where all the magic comes from, specially crafted for `useragent`.
6 var regexps = require('./lib/regexps');
9 * Reduce references by storing the lookups.
11 // OperatingSystem parsers:
12 var osparsers = regexps.os
13 , osparserslength = osparsers.length;
16 var agentparsers = regexps.browser
17 , agentparserslength = agentparsers.length;
20 var deviceparsers = regexps.device
21 , deviceparserslength = deviceparsers.length;
24 * The representation of a parsed user agent.
27 * @param {String} family The name of the browser
28 * @param {String} major Major version of the browser
29 * @param {String} minor Minor version of the browser
30 * @param {String} patch Patch version of the browser
31 * @param {String} source The actual user agent string
34 function Agent(family, major, minor, patch, source) {
35 this.family = family || 'Other';
36 this.major = major || '0';
37 this.minor = minor || '0';
38 this.patch = patch || '0';
39 this.source = source || '';
43 * OnDemand parsing of the Operating System.
45 * @type {OperatingSystem}
48 Object.defineProperty(Agent.prototype, 'os', {
49 get: function lazyparse() {
50 var userAgent = this.source
51 , length = osparserslength
57 for (; i < length; i++) {
58 if (res = parsers[i][0].exec(userAgent)) {
61 if (parser[1]) res[1] = parser[1].replace('$1', res[1]);
66 return Object.defineProperty(this, 'os', {
67 value: !parser || !res
68 ? new OperatingSystem()
69 : new OperatingSystem(
79 * Bypass the OnDemand parsing and set an OperatingSystem instance.
81 * @param {OperatingSystem} os
84 set: function set(os) {
85 if (!(os instanceof OperatingSystem)) return false;
87 return Object.defineProperty(this, 'os', {
94 * OnDemand parsing of the Device type.
99 Object.defineProperty(Agent.prototype, 'device', {
100 get: function lazyparse() {
101 var userAgent = this.source
102 , length = deviceparserslength
103 , parsers = deviceparsers
108 for (; i < length; i++) {
109 if (res = parsers[i][0].exec(userAgent)) {
112 if (parser[1]) res[1] = parser[1].replace('$1', res[1]);
117 return Object.defineProperty(this, 'device', {
118 value: !parser || !res
122 , parser[2] || res[2]
123 , parser[3] || res[3]
124 , parser[4] || res[4]
130 * Bypass the OnDemand parsing and set an Device instance.
132 * @param {Device} device
135 set: function set(device) {
136 if (!(device instanceof Device)) return false;
138 return Object.defineProperty(this, 'device', {
143 /*** Generates a string output of the parsed user agent.
148 Agent.prototype.toAgent = function toAgent() {
149 var output = this.family
150 , version = this.toVersion();
152 if (version) output += ' '+ version;
157 * Generates a string output of the parser user agent and operating system.
159 * @returns {String} "UserAgent 0.0.0 / OS"
162 Agent.prototype.toString = function toString() {
163 var agent = this.toAgent()
164 , os = this.os !== 'Other' ? this.os : false;
166 return agent + (os ? ' / ' + os : '');
170 * Outputs a compiled veersion number of the user agent.
175 Agent.prototype.toVersion = function toVersion() {
179 version += this.major;
182 version += '.' + this.minor;
184 // Special case here, the patch can also be Alpha, Beta etc so we need
185 // to check if it's a string or not.
187 version += (isNaN(+this.patch) ? ' ' : '.') + this.patch;
196 * Outputs a JSON string of the Agent.
201 Agent.prototype.toJSON = function toJSON() {
207 , device: this.device
213 * The representation of a parsed Operating System.
216 * @param {String} family The name of the os
217 * @param {String} major Major version of the os
218 * @param {String} minor Minor version of the os
219 * @param {String} patch Patch version of the os
222 function OperatingSystem(family, major, minor, patch) {
223 this.family = family || 'Other';
224 this.major = major || '0';
225 this.minor = minor || '0';
226 this.patch = patch || '0';
230 * Generates a stringified version of the Operating System.
232 * @returns {String} "Operating System 0.0.0"
235 OperatingSystem.prototype.toString = function toString() {
236 var output = this.family
237 , version = this.toVersion();
239 if (version) output += ' '+ version;
244 * Generates the version of the Operating System.
249 OperatingSystem.prototype.toVersion = function toVersion() {
253 version += this.major;
256 version += '.' + this.minor;
258 // Special case here, the patch can also be Alpha, Beta etc so we need
259 // to check if it's a string or not.
261 version += (isNaN(+this.patch) ? ' ' : '.') + this.patch;
270 * Outputs a JSON string of the OS, values are defaulted to undefined so they
271 * are not outputed in the stringify.
276 OperatingSystem.prototype.toJSON = function toJSON(){
279 , major: this.major || undefined
280 , minor: this.minor || undefined
281 , patch: this.patch || undefined
286 * The representation of a parsed Device.
289 * @param {String} family The name of the device
290 * @param {String} major Major version of the device
291 * @param {String} minor Minor version of the device
292 * @param {String} patch Patch version of the device
295 function Device(family, major, minor, patch) {
296 this.family = family || 'Other';
297 this.major = major || '0';
298 this.minor = minor || '0';
299 this.patch = patch || '0';
303 * Generates a stringified version of the Device.
305 * @returns {String} "Device 0.0.0"
308 Device.prototype.toString = function toString() {
309 var output = this.family
310 , version = this.toVersion();
312 if (version) output += ' '+ version;
317 * Generates the version of the Device.
322 Device.prototype.toVersion = function toVersion() {
326 version += this.major;
329 version += '.' + this.minor;
331 // Special case here, the patch can also be Alpha, Beta etc so we need
332 // to check if it's a string or not.
334 version += (isNaN(+this.patch) ? ' ' : '.') + this.patch;
343 * Outputs a JSON string of the Device, values are defaulted to undefined so they
344 * are not outputed in the stringify.
349 Device.prototype.toJSON = function toJSON() {
352 , major: this.major || undefined
353 , minor: this.minor || undefined
354 , patch: this.patch || undefined
359 * Small nifty thick that allows us to download a fresh set regexs from t3h
360 * Int3rNetz when we want to. We will be using the compiled version by default
361 * but users can opt-in for updates.
363 * @param {Boolean} refresh Refresh the dataset from the remote
366 module.exports = function updater() {
368 require('./lib/update').update(function updating(err, results) {
370 console.log('[useragent] Failed to update the parsed due to an error:');
371 console.log('[useragent] '+ (err.message ? err.message : err));
377 // OperatingSystem parsers:
378 osparsers = regexps.os;
379 osparserslength = osparsers.length;
381 // UserAgent parsers:
382 agentparsers = regexps.browser;
383 agentparserslength = agentparsers.length;
386 deviceparsers = regexps.device;
387 deviceparserslength = deviceparsers.length;
390 console.error('[useragent] If you want to use automatic updating, please add:');
391 console.error('[useragent] - request (npm install request --save)');
392 console.error('[useragent] - yamlparser (npm install yamlparser --save)');
393 console.error('[useragent] To your own package.json');
397 // Override the exports with our newly set module.exports
398 exports = module.exports;
401 * Nao that we have setup all the different classes and configured it we can
402 * actually start assembling and exposing everything.
404 exports.Device = Device;
405 exports.OperatingSystem = OperatingSystem;
406 exports.Agent = Agent;
409 * Check if the userAgent is something we want to parse with regexp's.
411 * @param {String} userAgent The userAgent.
414 function isSafe(userAgent) {
418 for (var i = 0; i < userAgent.length; i++) {
419 code = userAgent.charCodeAt(i);
420 // numbers between 0 and 9
421 if (code >= 48 && code <= 57) {
427 if (consecutive >= 100) {
437 * Parses the user agent string with the generated parsers from the
438 * ua-parser project on google code.
440 * @param {String} userAgent The user agent string
441 * @param {String} jsAgent Optional UA from js to detect chrome frame
445 exports.parse = function parse(userAgent, jsAgent) {
446 if (!userAgent || !isSafe(userAgent)) return new Agent();
448 var length = agentparserslength
449 , parsers = agentparsers
454 for (; i < length; i++) {
455 if (res = parsers[i][0].exec(userAgent)) {
458 if (parser[1]) res[1] = parser[1].replace('$1', res[1]);
459 if (!jsAgent) return new Agent(
461 , parser[2] || res[2]
462 , parser[3] || res[3]
463 , parser[4] || res[4]
471 // Return early if we didn't find an match, but might still be able to parse
472 // the os and device, so make sure we supply it with the source
473 if (!parser || !res) return new Agent('', '', '', '', userAgent);
475 // Detect Chrome Frame, but make sure it's enabled! So we need to check for
476 // the Chrome/ so we know that it's actually using Chrome under the hood.
477 if (jsAgent && ~jsAgent.indexOf('Chrome/') && ~userAgent.indexOf('chromeframe')) {
478 res[1] = 'Chrome Frame (IE '+ res[1] +'.'+ res[2] +')';
480 // Run the JavaScripted userAgent string through the parser again so we can
481 // update the version numbers;
482 parser = parse(jsAgent);
483 parser[2] = parser.major;
484 parser[3] = parser.minor;
485 parser[4] = parser.patch;
490 , parser[2] || res[2]
491 , parser[3] || res[3]
492 , parser[4] || res[4]
498 * If you are doing a lot of lookups you might want to cache the results of the
499 * parsed user agent string instead, in memory.
501 * @TODO We probably want to create 2 dictionary's here 1 for the Agent
502 * instances and one for the userAgent instance mapping so we can re-use simular
503 * Agent instance and lower our memory consumption.
505 * @param {String} userAgent The user agent string
506 * @param {String} jsAgent Optional UA from js to detect chrome frame
509 var LRU = require('lru-cache')(5000);
510 exports.lookup = function lookup(userAgent, jsAgent) {
511 var key = (userAgent || '')+(jsAgent || '')
512 , cached = LRU.get(key);
514 if (cached) return cached;
515 LRU.set(key, (cached = exports.parse(userAgent, jsAgent)));
521 * Does a more inaccurate but more common check for useragents identification.
522 * The version detection is from the jQuery.com library and is licensed under
525 * @param {String} useragent The user agent
526 * @returns {Object} matches
529 exports.is = function is(useragent) {
530 var ua = (useragent || '').toLowerCase()
535 , mobile_safari: false
541 , version: (ua.match(exports.is.versionRE) || [0, "0"])[1]
544 if (~ua.indexOf('webkit')) {
545 details.webkit = true;
547 if (~ua.indexOf('android')){
548 details.android = true;
551 if (~ua.indexOf('chrome')) {
552 details.chrome = true;
553 } else if (~ua.indexOf('safari')) {
554 details.safari = true;
556 if (~ua.indexOf('mobile') && ~ua.indexOf('apple')) {
557 details.mobile_safari = true;
560 } else if (~ua.indexOf('opera')) {
561 details.opera = true;
562 } else if (~ua.indexOf('trident') || ~ua.indexOf('msie')) {
564 } else if (~ua.indexOf('mozilla') && !~ua.indexOf('compatible')) {
565 details.mozilla = true;
567 if (~ua.indexOf('firefox')) details.firefox = true;
575 * Parses out the version numbers.
580 exports.is.versionRE = /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/;
583 * Transform a JSON object back to a valid userAgent string
585 * @param {Object} details
588 exports.fromJSON = function fromJSON(details) {
589 if (typeof details === 'string') details = JSON.parse(details);
591 var agent = new Agent(details.family, details.major, details.minor, details.patch)
594 // The device family was added in v2.0
595 if ('device' in details) {
596 agent.device = new Device(details.device.family);
598 agent.device = new Device();
601 if ('os' in details && os) {
602 // In v1.1.0 we only parsed out the Operating System name, not the full
603 // version which we added in v2.0. To provide backwards compatible we should
604 // we should set the details.os as family
605 if (typeof os === 'string') {
606 agent.os = new OperatingSystem(os);
608 agent.os = new OperatingSystem(os.family, os.major, os.minor, os.patch);
621 exports.version = require('./package.json').version;