diff --git a/readme.md b/readme.md index acf44a1..73bfa27 100644 --- a/readme.md +++ b/readme.md @@ -53,7 +53,7 @@ Usually you can find the user agent in: ## Constructor When you call `UAParser` with the `new` keyword `UAParser` will return a new instance with an empty result object, you have to call one of the available methods to get the information from the user-agent string. Like so: -* `new UAParser([user-agent:string][,extensions:object])` +* `new UAParser([user-agent:string][,extensions:object][,headers:object(since@1.1)])` ```js let parser = new UAParser("user-agent"); // you need to pass the user-agent for nodejs console.log(parser); // {} @@ -70,7 +70,7 @@ console.log(parserResults); ``` When you call UAParser without the `new` keyword, it will automatically call `getResult()` function and return the parsed results. -* `UAParser([user-agent:string][,extensions:object])` +* `UAParser([user-agent:string][,extensions:object][,headers:object(since@1.1)])` * returns result object `{ ua: '', browser: {}, cpu: {}, device: {}, engine: {}, os: {} }` ## Methods @@ -293,16 +293,17 @@ engine.toString(); // "Blink 28.0.1500.95" If you want to detect something that's not currently provided by UAParser.js (eg: bots, specific apps, etc), you can pass a list of regexes to extend internal UAParser.js regexes with your own. -* `UAParser([uastring,] extensions)` +* `UAParser([uastring,] extensions [,headers:object(since@1.1)])` ```js // Example: let myOwnListOfBrowsers = [ - [/(mybrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION] + [/(mybrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION, ['type', 'bot']] ]; let myParser = new UAParser({ browser: myOwnListOfBrowsers }); let myUA = 'Mozilla/5.0 MyBrowser/1.3'; -console.log(myParser.setUA(myUA).getBrowser()); // {name: "MyBrowser", version: "1.3"} +console.log(myParser.setUA(myUA).getBrowser()); // {name: "MyBrowser", version: "1.3", major: "1", type : "bot"} +console.log(myParser.getBrowser().is('bot')); // true // Another example: let myOwnListOfDevices = [ diff --git a/src/ua-parser.js b/src/ua-parser.js index a219a38..7a3dc7a 100755 --- a/src/ua-parser.js +++ b/src/ua-parser.js @@ -37,6 +37,7 @@ SMARTTV = 'smarttv', WEARABLE = 'wearable', EMBEDDED = 'embedded', + USER_AGENT = 'user-agent', UA_MAX_LENGTH = 350; var AMAZON = 'Amazon', @@ -71,11 +72,7 @@ var extend = function (regexes, extensions) { var mergedRegexes = {}; for (var i in regexes) { - if (extensions[i] && extensions[i].length % 2 === 0) { - mergedRegexes[i] = extensions[i].concat(regexes[i]); - } else { - mergedRegexes[i] = regexes[i]; - } + mergedRegexes[i] = extensions[i] && extensions[i].length % 2 === 0 ? extensions[i].concat(regexes[i]) : regexes[i]; } return mergedRegexes; }, @@ -89,6 +86,11 @@ has = function (str1, str2) { return typeof str1 === STR_TYPE ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false; }, + isExtensions = function (obj) { + for (var prop in obj) { + return /^(browser|cpu|device|engine|os)$/.test(prop); + } + }, lowerize = function (str, rgx) { return typeof(str) === STR_TYPE ? str.toLowerCase().replace((rgx ? new RegExp(rgx, 'i') : EMPTY), EMPTY) : str; }, @@ -803,84 +805,83 @@ return callback(this.data); }; UAItem.createUAData = function (data) { - return (function () { - var propIs = data.propIs; - var ignoreIs = data.ignoreIs; - var propToStr = data.propToStr; - var UAData = function () { - for (var i in data.props) { - this[data.props[i]] = undefined; + var is_ignoreProps = data.is_ignoreProps, + is_ignoreRgx = data.is_ignoreRgx, + toString_props = data.toString_props; + + var UAData = function () { + for (var i in data.init_props) { + this[data.init_props[i]] = undefined; + } + }; + UAData.prototype.is = function (strToCheck) { + var is = false; + for (var i in this) { + if (this.hasOwnProperty(i) && !is_ignoreProps[i] && lowerize(this[i], is_ignoreRgx) === lowerize(strToCheck, is_ignoreRgx)) { + is = true; + if (strToCheck != UNDEF_TYPE) break; + } else if (strToCheck == UNDEF_TYPE && is) { + is = !is; + break; } - }; - UAData.prototype.is = function (strToCheck) { - var is = false; - for (var i in propIs) { - if (lowerize(this[propIs[i]], ignoreIs) === lowerize(strToCheck, ignoreIs)) { - is = true; - if (strToCheck != UNDEF_TYPE) break; - } else if (strToCheck == UNDEF_TYPE && is) { - is = !is; - break; - } + } + return is; + }; + UAData.prototype.toString = function () { + var str = EMPTY; + for (var i in toString_props) { + if (typeof(this[toString_props[i]]) !== UNDEF_TYPE) { + str += (str ? ' ' : EMPTY) + this[toString_props[i]]; } - return is; - }; - UAData.prototype.toString = function () { - var str = EMPTY; - for (var i in propToStr) { - if (typeof(this[propToStr[i]]) !== UNDEF_TYPE) { - str += (str ? ' ' : EMPTY) + this[propToStr[i]]; - } - } - return str ? str : UNDEF_TYPE; - }; - return new UAData(); - })(data); + } + return str ? str : UNDEF_TYPE; + }; + return new UAData(); }; function UABrowser () { this.data = UAItem.createUAData({ - props : [NAME, VERSION, MAJOR], - propIs : [NAME], - ignoreIs : ' ?browser$', - propToStr : [NAME, VERSION] + init_props : [NAME, VERSION, MAJOR], + is_ignoreProps : [VERSION, MAJOR], + is_ignoreRgx : ' ?browser$', + toString_props : [NAME, VERSION] }); } UABrowser.prototype = new UAItem(); function UACPU () { this.data = UAItem.createUAData({ - props : [ARCHITECTURE], - propIs : [ARCHITECTURE], - propToStr : [ARCHITECTURE] + init_props : [ARCHITECTURE], + is_ignoreProps : [], + toString_props : [ARCHITECTURE] }); } UACPU.prototype = new UAItem(); function UADevice () { this.data = UAItem.createUAData({ - props : [TYPE, MODEL, VENDOR], - propIs : [TYPE, MODEL, VENDOR], - propToStr : [VENDOR, MODEL] + init_props : [TYPE, MODEL, VENDOR], + is_ignoreProps : [], + toString_props : [VENDOR, MODEL] }); } UADevice.prototype = new UAItem(); function UAEngine () { this.data = UAItem.createUAData({ - props : [NAME, VERSION], - propIs : [NAME], - propToStr : [NAME, VERSION] + init_props : [NAME, VERSION], + is_ignoreProps : [VERSION], + toString_props : [NAME, VERSION] }); } UAEngine.prototype = new UAItem(); function UAOS () { this.data = UAItem.createUAData({ - props : [NAME, VERSION], - propIs : [NAME], - ignoreIs : ' ?os$', - propToStr : [NAME, VERSION] + init_props : [NAME, VERSION], + is_ignoreProps : [VERSION], + is_ignoreRgx : ' ?os$', + toString_props : [NAME, VERSION] }); } UAOS.prototype = new UAItem(); @@ -897,18 +898,30 @@ } UAResult.prototype = new UAItem(); - function UAParser (ua, extensions) { + function UAParser (ua, extensions, headers) { if (typeof ua === OBJ_TYPE) { - extensions = ua; + if (isExtensions(ua)) { + if (typeof extensions === OBJ_TYPE) { + headers = extensions; // case UAParser(extensions, headers) + } + extensions = ua; // case UAParser(extensions) + } else { + headers = ua; // case UAParser(headers) + } ua = undefined; + } else if (typeof ua === STR_TYPE && !isExtensions(extensions)) { + headers = extensions; // case UAParser(ua, headers) + extensions = undefined; } + if (!(this instanceof UAParser)) { - return new UAParser(ua, extensions).getResult(); + return new UAParser(ua, extensions, headers).getResult(); } - var _navigator = (typeof window !== UNDEF_TYPE && window.navigator) ? window.navigator : undefined; - var _ua = ua || ((_navigator && _navigator.userAgent) ? _navigator.userAgent : EMPTY); + + // _ua = user-supplied string || window.navigator.userAgent || user-agent header || empty + var _ua = ua || ((_navigator && _navigator.userAgent) ? _navigator.userAgent : (!ua && headers && headers[USER_AGENT] ? headers[USER_AGENT] : EMPTY)); var _uach = (_navigator && _navigator.userAgentData) ? _navigator.userAgentData : undefined; var _rgxmap = extensions ? extend(regexes, extensions) : regexes; diff --git a/test/test.js b/test/test.js index 7b4e4a4..339f3c3 100644 --- a/test/test.js +++ b/test/test.js @@ -106,6 +106,25 @@ describe('Extending Regex', function () { parser2.setUA(uaString); assert.strictEqual(parser2.getBrowser().name, 'MyOwnBrowser'); assert.strictEqual(parser1.getBrowser().version, '1.3'); + + let myOwnListOfBrowsers = [ + [/(mybrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION, ['type', 'bot']] + ]; + let myParser = new UAParser({ browser: myOwnListOfBrowsers }); + let myUA = 'Mozilla/5.0 MyBrowser/1.3'; + assert.deepEqual(myParser.setUA(myUA).getBrowser(), {name: "MyBrowser", version: "1.3", major: "1", type : "bot"}); + assert.strictEqual(myParser.getBrowser().is('bot'), true); + + let myOwnListOfDevices = [ + [/(mytab) ([\w ]+)/i], [UAParser.DEVICE.VENDOR, UAParser.DEVICE.MODEL, [UAParser.DEVICE.TYPE, UAParser.DEVICE.TABLET]], + [/(myphone)/i], [UAParser.DEVICE.VENDOR, [UAParser.DEVICE.TYPE, UAParser.DEVICE.MOBILE]] + ]; + let myParser2 = new UAParser({ + browser: myOwnListOfBrowsers, + device: myOwnListOfDevices + }); + let myUA2 = 'Mozilla/5.0 MyTab 14 Pro Max'; + assert.deepEqual(myParser2.setUA(myUA2).getDevice(), {vendor: "MyTab", model: "14 Pro Max", type: "tablet"}); }); describe('User-agent length', function () { @@ -248,25 +267,72 @@ describe('is() utility method', function () { }); describe('toString() utility method', function () { - let uap = new UAParser('Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537'); - assert.strictEqual(uap.getBrowser().name, "IEMobile"); - assert.strictEqual(uap.getBrowser().version, "11.0"); - assert.strictEqual(uap.getBrowser().major, "11"); - assert.strictEqual(uap.getBrowser().toString(), "IEMobile 11.0"); + it('Should return full name', function () { + let uap = new UAParser('Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537'); + assert.strictEqual(uap.getBrowser().name, "IEMobile"); + assert.strictEqual(uap.getBrowser().version, "11.0"); + assert.strictEqual(uap.getBrowser().major, "11"); + assert.strictEqual(uap.getBrowser().toString(), "IEMobile 11.0"); - assert.strictEqual(uap.getCPU().architecture, "arm"); - assert.strictEqual(uap.getCPU().toString(), "arm"); + assert.strictEqual(uap.getCPU().architecture, "arm"); + assert.strictEqual(uap.getCPU().toString(), "arm"); - assert.strictEqual(uap.getDevice().vendor, "Nokia"); - assert.strictEqual(uap.getDevice().model, "Lumia 635"); - assert.strictEqual(uap.getDevice().type, "mobile"); - assert.strictEqual(uap.getDevice().toString(), "Nokia Lumia 635"); + assert.strictEqual(uap.getDevice().vendor, "Nokia"); + assert.strictEqual(uap.getDevice().model, "Lumia 635"); + assert.strictEqual(uap.getDevice().type, "mobile"); + assert.strictEqual(uap.getDevice().toString(), "Nokia Lumia 635"); - assert.strictEqual(uap.getEngine().name, "Trident"); - assert.strictEqual(uap.getEngine().version, "7.0"); - assert.strictEqual(uap.getEngine().toString(), "Trident 7.0"); + assert.strictEqual(uap.getEngine().name, "Trident"); + assert.strictEqual(uap.getEngine().version, "7.0"); + assert.strictEqual(uap.getEngine().toString(), "Trident 7.0"); - assert.strictEqual(uap.getOS().name, "Windows Phone"); - assert.strictEqual(uap.getOS().version, "8.1"); - assert.strictEqual(uap.getOS().toString(), "Windows Phone 8.1"); + assert.strictEqual(uap.getOS().name, "Windows Phone"); + assert.strictEqual(uap.getOS().version, "8.1"); + assert.strictEqual(uap.getOS().toString(), "Windows Phone 8.1"); + }); +}); + +describe('Read user-agent data from req.headers', function () { + const ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)'; + const ext = { + engine : [ + [/(msie)/i], [[UAParser.ENGINE.NAME, 'Custom Browser 1']], + [/(edge)/i], [[UAParser.ENGINE.NAME, 'Custom Browser 2']] + ] + }; + const req = { + headers : { + 'user-agent' : 'Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36 Edge/12.0' + } + }; + + it('Can be called with UAParser(ua)', function () { + let engine = UAParser(ua).engine; + assert.strictEqual(engine.name, "Trident"); + }); + + it('Can be called with UAParser(ua, extensions)', function () { + let engine = UAParser(ua, ext).engine; + assert.strictEqual(engine.name, "Custom Browser 1"); + }); + + it('Can be called with UAParser(ua, extensions, headers)', function () { + let engine = UAParser(ua, ext, req.headers).engine; + assert.strictEqual(engine.name, "Custom Browser 1"); + }); + + it('Can be called with UAParser(ua, headers)', function () { + let engine = UAParser(ua, req.headers).engine; + assert.strictEqual(engine.name, "Trident"); + }); + + it('Can be called with UAParser(extensions, headers)', function () { + let engine = UAParser(ext, req.headers).engine; + assert.strictEqual(engine.name, "Custom Browser 2"); + }); + + it('Can be called with UAParser(headers)', function () { + let engine = UAParser(req.headers).engine; + assert.strictEqual(engine.name, "EdgeHTML"); + }); }); \ No newline at end of file