diff --git a/package.json b/package.json index df2cc2d1..db710b2b 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ ], "scripts": { "build": "uglifyjs src/ua-parser.js -o dist/ua-parser.min.js --comments '/^ UA/' && uglifyjs src/ua-parser.js -o dist/ua-parser.pack.js --comments '/^ UA/' --compress --mangle && node -e \"const fs=require('fs');fs.writeFileSync('dist/ua-parser.mjs','// Generated ESM version of UAParser.js\\n// Source file: /src/ua-parser.js\\n\\n'+fs.readFileSync('src/ua-parser.js','utf-8').replace(/\\(func[\\s\\S]+strict\\';/ig,'const window = undefined;').replace(/\\/[\\/\\s]+export[\\s\\S]+/ig,'export {UAParser};'),'utf-8')\"", - "test": "jshint src/ua-parser.js && mocha -R nyan test/test.js", + "test": "jshint src/ua-parser.js && mocha -R spec test/test.js && mocha -R spec test/es6-test.mjs", "test-ci": "jshint src/ua-parser.js && mocha -R spec test/test.js", "verup": "node ./node_modules/verup", "version": "node ./node_modules/verup 0" diff --git a/readme.md b/readme.md index 2733a300..90e72730 100644 --- a/readme.md +++ b/readme.md @@ -102,19 +102,19 @@ The methods are self explanatory, here's a small overview on all the available m # Possible 'browser.name': 2345Explorer, 360 Browser, Amaya, Android Browser, Arora, Avant, Avast, AVG, BIDUBrowser, Baidu, Basilisk, Blazer, Bolt, Brave, Bowser, Camino, Chimera, -Chrome Headless, Chrome WebView, Chrome, Chromium, Cobalt, Comodo Dragon, Dillo, +Chrome [Mobile/Headless/WebView], Chromium, Cobalt, Comodo Dragon, Dillo, Dolphin, Doris, DuckDuckGo, Edge, Electron, Epiphany, Facebook, Falkon, Fennec, -Firebird, Firefox [Focus/Reality], Flock, Flow, GSA, GoBrowser, Huawei Browser, -ICE Browser, IE, IEMobile, IceApe, IceCat, IceDragon, Iceweasel, Instagram, -Iridium, Iron, Jasmine, Kakao[Story/Talk], K-Meleon, Kindle, Klar, Konqueror, -LBBROWSER, Line, LinkedIn, Links, Lunascape, Lynx, MIUI Browser, Maemo Browser, -Maemo, Maxthon, MetaSr Midori, Minimo, Mobile Safari, Mosaic, Mozilla, NetFront, +Firebird, Firefox [Mobile/Focus/Reality], Flock, Flow, GSA, GoBrowser, +Huawei Browser, ICE Browser, IE, IEMobile, IceApe, IceCat, IceDragon, Iceweasel, +Instagram, Iridium, Iron, Jasmine, Kakao[Story/Talk], K-Meleon, Kindle, Klar, +Konqueror, LBBROWSER, Line, LinkedIn, Links, Lunascape, Lynx, MIUI Browser, +Maemo Browser, Maemo, Maxthon, MetaSr Midori, Minimo, Mosaic, Mozilla, NetFront, NetSurf, Netfront, Netscape, NokiaBrowser, Obigo, Oculus Browser, OmniWeb, Opera Coast, Opera [Mini/Mobi/Tablet], PaleMoon, PhantomJS, Phoenix, Polaris, -Puffin, QQ, QQBrowser, QQBrowserLite, Quark, QupZilla, RockMelt, Safari, +Puffin, QQ, QQBrowser, QQBrowserLite, Quark, QupZilla, RockMelt, Safari [Mobile], Sailfish Browser, Samsung Browser, SeaMonkey, Silk, Skyfire, Sleipnir, Slim, SlimBrowser, Swiftfox, Tesla, Tizen Browser, UCBrowser, UP.Browser, Viera, -Vivaldi, Waterfox, WeChat, Weibo, Yandex, baidu, iCab, w3m, Whale Browser... +Vivaldi, Waterfox, WeChat, Weibo, Yandex, baidu, iCab, w3m, Whale Browser, ... # 'browser.version' determined dynamically ``` diff --git a/src/ua-parser.js b/src/ua-parser.js index d5daf6e0..363482d1 100755 --- a/src/ua-parser.js +++ b/src/ua-parser.js @@ -52,39 +52,37 @@ CH_HEADER_MODEL = CH_HEADER + '-model', CH_HEADER_PLATFORM = CH_HEADER + '-platform', CH_HEADER_PLATFORM_VER = CH_HEADER_PLATFORM + '-version', - CH_ALL_VALUES = ['brands', 'fullVersionList', MOBILE, MODEL, 'platform', 'platformVersion', ARCHITECTURE, 'bitness'], + CH_ALL_VALUES = ['brands', 'fullVersionList', MOBILE, MODEL, 'platform', 'platformVersion', ARCHITECTURE, 'bitness'], UA_BROWSER = 'browser', UA_CPU = 'cpu', UA_DEVICE = 'device', UA_ENGINE = 'engine', UA_OS = 'os', - UA_RESULT = 'result'; - - var AMAZON = 'Amazon', - APPLE = 'Apple', - ASUS = 'ASUS', - BLACKBERRY = 'BlackBerry', - GOOGLE = 'Google', - HUAWEI = 'Huawei', - LG = 'LG', - MICROSOFT = 'Microsoft', - MOTOROLA = 'Motorola', - SAMSUNG = 'Samsung', - SHARP = 'Sharp', - SONY = 'Sony', - SWISS = 'Swiss', - XIAOMI = 'Xiaomi', - ZEBRA = 'Zebra', - ZTE = 'ZTE', - - BROWSER = 'Browser', - CHROME = 'Chrome', - EDGE = 'Edge', - FIREFOX = 'Firefox', - OPERA = 'Opera', + UA_RESULT = 'result', + AMAZON = 'Amazon', + APPLE = 'Apple', + ASUS = 'ASUS', + BLACKBERRY = 'BlackBerry', + GOOGLE = 'Google', + HUAWEI = 'Huawei', + LG = 'LG', + MICROSOFT = 'Microsoft', + MOTOROLA = 'Motorola', + SAMSUNG = 'Samsung', + SHARP = 'Sharp', + SONY = 'Sony', + SWISS = 'Swiss', + XIAOMI = 'Xiaomi', + ZEBRA = 'Zebra', + ZTE = 'ZTE', + SUFFIX_BROWSER = 'Browser', + SUFFIX_MOBILE = 'Mobile', + CHROME = 'Chrome', + EDGE = 'Edge', + FIREFOX = 'Firefox', + OPERA = 'Opera', FACEBOOK = 'Facebook', - - WINDOWS = 'Windows'; + WINDOWS = 'Windows'; var NAVIGATOR = (typeof window !== UNDEF_TYPE && window.navigator) ? window.navigator : @@ -97,7 +95,18 @@ // Helper ////////// - var extend = function (regexes, extensions) { + var assignFromEntries = function (arr) { + for (var i in arr) { + var propName = arr[i]; + if (typeof propName == OBJ_TYPE && propName.length == 2) { + this[propName[0]] = propName[1]; + } else { + this[propName] = undefined; + } + } + return this; + }, + extend = function (regexes, extensions) { var mergedRegexes = {}; for (var i in regexes) { mergedRegexes[i] = extensions[i] && extensions[i].length % 2 === 0 ? extensions[i].concat(regexes[i]) : regexes[i]; @@ -112,18 +121,13 @@ return enums; }, has = function (str1, str2) { - return typeof str1 === STR_TYPE ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false; - }, - initialize = function (arr) { - for (var i in arr) { - var propName = arr[i]; - if (typeof propName == OBJ_TYPE && propName.length == 2) { - this[propName[0]] = propName[1]; - } else { - this[propName] = undefined; + if (typeof str1 === OBJ_TYPE && str1.length > 0) { + for (var i in str1) { + if (lowerize(str1[i]) == lowerize(str2)) return true; } + return false; } - return this; + return typeof str1 === STR_TYPE ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false; }, isExtensions = function (obj) { for (var prop in obj) { @@ -140,8 +144,8 @@ } return arr; }, - lowerize = function (str, rgx) { - return typeof(str) === STR_TYPE ? strip((rgx ? new RegExp(rgx, 'i') : EMPTY), str.toLowerCase()) : str; + lowerize = function (str) { + return typeof(str) === STR_TYPE ? str.toLowerCase() : str; }, majorize = function (version) { return typeof(version) === STR_TYPE ? strip(/[^\d\.]/g, version).split('.')[0] : undefined; @@ -272,7 +276,7 @@ // Regex map ///////////// - var regexes = { + var defaultRegexes = { browser : [[ @@ -305,7 +309,7 @@ /(weibo)__([\d\.]+)/i // Weibo ], [NAME, VERSION], [ /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i // UCBrowser - ], [VERSION, [NAME, 'UC'+BROWSER]], [ + ], [VERSION, [NAME, 'UC'+SUFFIX_BROWSER]], [ /microm.+\bqbcore\/([\w\.]+)/i, // WeChat Desktop for Windows Built-in Browser /\bqbcore\/([\w\.]+).+microm/i ], [VERSION, [NAME, 'WeChat(Win) Desktop']], [ @@ -318,7 +322,7 @@ /yabrowser\/([\w\.]+)/i // Yandex ], [VERSION, [NAME, 'Yandex']], [ /(avast|avg)\/([\w\.]+)/i // Avast/AVG Secure Browser - ], [[NAME, /(.+)/, '$1 Secure '+BROWSER], VERSION], [ + ], [[NAME, /(.+)/, '$1 Secure '+SUFFIX_BROWSER], VERSION], [ /\bfocus\/([\w\.]+)/i // Firefox Focus ], [VERSION, [NAME, FIREFOX+' Focus']], [ /\bopt\/([\w\.]+)/i // Opera Touch @@ -330,13 +334,13 @@ /coast\/([\w\.]+)/i // Opera Coast ], [VERSION, [NAME, OPERA+' Coast']], [ /miuibrowser\/([\w\.]+)/i // MIUI Browser - ], [VERSION, [NAME, 'MIUI '+BROWSER]], [ - /fxios\/([-\w\.]+)/i // Firefox for iOS - ], [VERSION, [NAME, FIREFOX]], [ + ], [VERSION, [NAME, 'MIUI '+SUFFIX_BROWSER]], [ + /fxios\/([\w\.-]+)/i // Firefox for iOS + ], [VERSION, [NAME, 'Firefox '+SUFFIX_MOBILE]], [ /\bqihu|(qi?ho?o?|360)browser/i // 360 - ], [[NAME, '360 '+BROWSER]], [ + ], [[NAME, '360 '+SUFFIX_BROWSER]], [ /(oculus|samsung|sailfish|huawei)browser\/([\w\.]+)/i - ], [[NAME, /(.+)/, '$1 '+BROWSER], VERSION], [ // Oculus/Samsung/Sailfish/Huawei Browser + ], [[NAME, /(.+)/, '$1 '+SUFFIX_BROWSER], VERSION], [ // Oculus/Samsung/Sailfish/Huawei Browser /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon ], [[NAME, /_/g, ' '], VERSION], [ /(electron)\/([\w\.]+) safari/i, // Electron-based App @@ -367,14 +371,18 @@ ], [[NAME, CHROME+' WebView'], VERSION], [ /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i // Android Browser - ], [VERSION, [NAME, 'Android '+BROWSER]], [ + ], [VERSION, [NAME, 'Android '+SUFFIX_BROWSER]], [ + + /chrome\/([\w\.]+) mobile/i, // Chrome Mobile + /(?:(?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS + ], [VERSION, [NAME, 'Chrome '+SUFFIX_MOBILE]], [ /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i // Chrome/OmniWeb/Arora/Tizen/Nokia ], [NAME, VERSION], [ - /version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i // Mobile Safari - ], [VERSION, [NAME, 'Mobile Safari']], [ - /version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i // Safari & Safari Mobile + /version\/([\w\.\,]+) .*mobile(?:\/\w+ | ?)safari/i // Safari Mobile + ], [VERSION, [NAME, 'Safari '+SUFFIX_MOBILE]], [ + /version\/([\w\.\,]+) .*(safari)/i // Safari ], [VERSION, NAME], [ /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i // Safari < 3.0 ], [NAME, [VERSION, strMapper, oldSafariMap]], [ @@ -383,6 +391,8 @@ ], [NAME, VERSION], [ // Gecko based + /(?:mobile|tablet);.*(firefox)\/([\w\.-]+)/i // Firefox Mobile + ], [[NAME, 'Firefox '+SUFFIX_MOBILE], VERSION], [ /(navigator|netscape\d?)\/([-\w\.]+)/i // Netscape ], [[NAME, 'Netscape'], VERSION], [ /mobile vr; rv:([\w\.]+)\).+firefox/i // Firefox Reality @@ -841,15 +851,108 @@ ] }; + ///////////////// + // Factories + //////////////// + + var defaultProps = (function () { + var props = { init : {}, isIgnore : {}, isIgnoreRgx : {}, toString : {}}; + assignFromEntries.call(props.init, [ + [UA_BROWSER, [NAME, VERSION, MAJOR]], + [UA_CPU, [ARCHITECTURE]], + [UA_DEVICE, [TYPE, MODEL, VENDOR]], + [UA_ENGINE, [NAME, VERSION]], + [UA_OS, [NAME, VERSION]] + ]); + assignFromEntries.call(props.isIgnore, [ + [UA_BROWSER, [VERSION, MAJOR]], + [UA_ENGINE, [VERSION]], + [UA_OS, [VERSION]] + ]); + assignFromEntries.call(props.isIgnoreRgx, [ + [UA_BROWSER, / ?browser$/i], + [UA_OS, / ?os$/i] + ]); + assignFromEntries.call(props.toString, [ + [UA_BROWSER, [NAME, VERSION]], + [UA_CPU, [ARCHITECTURE]], + [UA_DEVICE, [VENDOR, MODEL]], + [UA_ENGINE, [NAME, VERSION]], + [UA_OS, [NAME, VERSION]] + ]); + return props; + })(); + + var createUAParserData = function (itemType, ua, rgxMap, uaCH) { + + var init_props = defaultProps.init[itemType], + is_ignoreProps = defaultProps.isIgnore[itemType] || 0, + is_ignoreRgx = defaultProps.isIgnoreRgx[itemType] || 0, + toString_props = defaultProps.toString[itemType] || 0; + + function UAParserData () { + assignFromEntries.call(this, init_props); + } + UAParserData.prototype.withClientHints = function () { + + // nodejs / non-client-hints browsers + if (!NAVIGATOR_UADATA) { + return new UAParserItem(itemType, ua, rgxMap, uaCH).parseCH().get(); + } + + // browsers based on chromium 85+ + return NAVIGATOR_UADATA + .getHighEntropyValues(CH_ALL_VALUES) + .then(function (res) { + var JS_UACH = new UAParserDataCH(res, false); + return new UAParserItem(itemType, ua, rgxMap, JS_UACH).parseCH().get(); + }); + }; + + if (itemType != UA_RESULT) { + UAParserData.prototype.is = function (strToCheck) { + var is = false; + for (var i in this) { + if (this.hasOwnProperty(i) && !has(is_ignoreProps, i) && lowerize(is_ignoreRgx ? strip(is_ignoreRgx, this[i]) : this[i]) == lowerize(is_ignoreRgx ? strip(is_ignoreRgx, strToCheck) : strToCheck)) { + is = true; + if (strToCheck != UNDEF_TYPE) break; + } else if (strToCheck == UNDEF_TYPE && is) { + is = !is; + break; + } + } + return is; + }; + UAParserData.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 str || UNDEF_TYPE; + }; + } + + if (!NAVIGATOR_UADATA) { + UAParserData.prototype.then = function (cb) { + cb(this); + return this; + }; + } + + return new UAParserData(); + }; + ///////////////// // Constructor //////////////// function UAParserDataCH (uach, isHTTP_UACH) { uach = uach || {}; - initialize.call(this, CH_ALL_VALUES); + assignFromEntries.call(this, CH_ALL_VALUES); if (isHTTP_UACH) { - initialize.call(this, [ + assignFromEntries.call(this, [ [BRANDS, itemListToArray(uach[CH_HEADER])], [FULLVERLIST, itemListToArray(uach[CH_HEADER_FULL_VER_LIST])], [BRANDS, itemListToArray(uach[CH_HEADER])], @@ -868,268 +971,126 @@ return this; } - function UAParserItem (data) { - if (!data) return; - this.ua = data[0]; - this.uaCH = data[1]; - this.rgxMap = data[3]; - this.data = (function (data) { - var ua = data[0], - uaCH = data[1], - itemType = data[2], - rgxMap = data[3], - init_props = data[4], - is_ignoreProps = data[5], - is_ignoreRgx = data[6], - toString_props = data[7]; - - function UAParserData () { - initialize.call(this, init_props); - } - UAParserData.prototype.withClientHints = function () { - - // nodejs / non-client-hints browsers - if (!NAVIGATOR_UADATA) { - - var HTTP_UACH = uaCH; - switch (itemType) { - case UA_BROWSER: - return new UAParserBrowser(ua, rgxMap, HTTP_UACH).parseCH().get(); - case UA_CPU: - return new UAParserCPU(ua, rgxMap, HTTP_UACH).parseCH().get(); - case UA_DEVICE: - return new UAParserDevice(ua, rgxMap, HTTP_UACH).parseCH().get(); - case UA_ENGINE: - return new UAParserEngine(ua, rgxMap).get(); - case UA_OS: - return new UAParserOS(ua, rgxMap, HTTP_UACH).parseCH().get(); - default : - return new UAParserResult(ua, rgxMap, HTTP_UACH) - .set('ua', ua) - .set('ua_ch', uaCH) - .set(UA_BROWSER, new UAParserBrowser(ua, rgxMap[UA_BROWSER], HTTP_UACH).parseCH().get()) - .set(UA_CPU, new UAParserCPU(ua, rgxMap[UA_CPU], HTTP_UACH).parseCH().get()) - .set(UA_DEVICE, new UAParserDevice(ua, rgxMap[UA_DEVICE], HTTP_UACH).parseCH().get()) - .set(UA_ENGINE, new UAParserEngine(ua, rgxMap[UA_ENGINE]).get()) - .set(UA_OS, new UAParserOS(ua, rgxMap[UA_OS], HTTP_UACH).parseCH().get()) - .get(); - } + function UAParserItem (itemType, ua, rgxMap, uaCH) { + assignFromEntries.call(this, [ + ['itemType', itemType], + ['ua', ua], + ['uaCH', uaCH], + ['rgxMap', rgxMap], + ['data', createUAParserData(itemType, ua, rgxMap, uaCH)] + ]); + this.parse(); + switch(this.itemType) { + case UA_BROWSER: + // Brave-specific detection + if (NAVIGATOR && NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == FUNC_TYPE) { + this.set(NAME, 'Brave'); } - - // browsers based on chromium 85+ - return NAVIGATOR_UADATA - .getHighEntropyValues(CH_ALL_VALUES) - .then(function (res) { - - var JS_UACH = new UAParserDataCH(res, false); - switch (itemType) { - case UA_BROWSER: - return UAParserBrowser(ua, rgxMap, JS_UACH).parseCH().get(); - case UA_CPU: - return new UAParserCPU(ua, rgxMap, JS_UACH).parseCH().get(); - case UA_DEVICE: - return new UAParserDevice(ua, rgxMap, JS_UACH).parseCH().get(); - case UA_ENGINE: - return new UAParserEngine(ua, rgxMap).get(); - case UA_OS: - return new UAParserOS(ua, rgxMap, JS_UACH).parseCH().get(); - default : - return new UAParserResult(ua, rgxMap, JS_UACH) - .set('ua', ua) - .set('ua_ch', JS_UACH) - .set(UA_BROWSER, new UAParserBrowser(ua, rgxMap[UA_BROWSER], JS_UACH).parseCH().get()) - .set(UA_CPU, new UAParserCPU(ua, rgxMap[UA_CPU], JS_UACH).parseCH().get()) - .set(UA_DEVICE, new UAParserDevice(ua, rgxMap[UA_DEVICE], JS_UACH).parseCH().get()) - .set(UA_ENGINE, new UAParserEngine(ua, rgxMap[UA_ENGINE]).get()) - .set(UA_OS, new UAParserOS(ua, rgxMap[UA_OS], JS_UACH).parseCH().get()) - .get(); - } - }); - }; - if(itemType != UA_RESULT) { - UAParserData.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; - } - } - return is; - }; - UAParserData.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 str ? str : UNDEF_TYPE; - }; - } - if (!NAVIGATOR_UADATA) { - UAParserData.prototype.then = function (cb) { - cb(this); - return this; + this.set(MAJOR, majorize(this.get(VERSION))); + break; + case UA_DEVICE: + if (!this.get(TYPE) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[MOBILE]) { + this.set(TYPE, MOBILE); + } + // iPadOS-specific detection: identified as Mac, but has some iOS-only properties + if (this.get(NAME) == 'Macintosh' && NAVIGATOR && typeof NAVIGATOR.standalone !== UNDEF_TYPE && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) { + this.set(MODEL, 'iPad') + .set(TYPE, TABLET); + } + break; + case UA_OS: + if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[PLATFORM] && NAVIGATOR_UADATA[PLATFORM] != 'Unknown') { + this.set(NAME, NAVIGATOR_UADATA[PLATFORM]); + } + break; + case UA_RESULT: + var createUAParserItem = function (itemType) { + return new UAParserItem(itemType, ua, rgxMap, uaCH).get(); }; - } - return new UAParserData(); - })(data); + this.set('ua', ua) + .set('ua_ch', uaCH) + .set(UA_BROWSER, createUAParserItem(UA_BROWSER)) + .set(UA_CPU, createUAParserItem(UA_CPU)) + .set(UA_DEVICE, createUAParserItem(UA_DEVICE)) + .set(UA_ENGINE, createUAParserItem(UA_ENGINE)) + .set(UA_OS, createUAParserItem(UA_OS)) + .get(); + } + return this; } UAParserItem.prototype.get = function (prop) { if (!prop) return this.data; return this.data.hasOwnProperty(prop) ? this.data[prop] : undefined; }; UAParserItem.prototype.parse = function () { - rgxMapper.call(this.data, this.ua, this.rgxMap); - return this; - }; - UAParserItem.prototype.set = function (prop, val) { - this.data[prop] = val; - return this; - }; - - function UAParserBrowser (ua, browserMap, uach) { - UAParserItem.call(this, [ - ua, - uach, - UA_BROWSER, - browserMap, - [NAME, VERSION, MAJOR], - [VERSION, MAJOR], - ' ?browser$', - [NAME, VERSION] - ]); - this.parse(); - // Brave-specific detection - if (NAVIGATOR && NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == FUNC_TYPE) { - this.set(NAME, 'Brave'); - } - this.set(MAJOR, majorize(this.get(VERSION))); - } - UAParserBrowser.prototype = new UAParserItem(); - UAParserBrowser.prototype.parseCH = function () { - var brands = this.uaCH[FULLVERLIST] || this.uaCH[BRANDS]; - if (brands) { - for (var i in brands) { - var brandName = brands[i].brand, - brandVersion = brands[i].version; - if (!/not.a.brand/i.test(brandName) || /chromi/i.test(this.get(NAME))) { - this.set(NAME, strip(GOOGLE+' ', brandName)) - .set(VERSION, brandVersion) - .set(MAJOR, majorize(brandVersion)); - } - } + if (this.itemType != UA_RESULT) { + rgxMapper.call(this.data, this.ua, this.rgxMap[this.itemType]); } return this; }; - - function UAParserCPU (ua, cpuMap, uach) { - UAParserItem.call(this, [ - ua, - uach, - UA_CPU, - cpuMap, - [ARCHITECTURE], - [], - null, - [ARCHITECTURE] - ]); - this.parse(); - } - UAParserCPU.prototype = new UAParserItem(); - UAParserCPU.prototype.parseCH = function () { - var archName = this.uaCH[ARCHITECTURE]; - if (archName) { - archName += (archName && this.uaCH[BITNESS] == '64') ? '64' : EMPTY; - rgxMapper.call(this.data, archName, this.rgxMap); - } - return this; - }; - - function UAParserDevice (ua, deviceMap, uach) { - UAParserItem.call(this, [ - ua, - uach, - UA_DEVICE, - deviceMap, - [TYPE, MODEL, VENDOR], - [], - null, - [VENDOR, MODEL] - ]); - this.parse(); - if (!this.get(TYPE) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[MOBILE]) { - this.set(TYPE, MOBILE); - } - // iPadOS-specific detection: identified as Mac, but has some iOS-only properties - if (this.get(NAME) == 'Macintosh' && NAVIGATOR && typeof NAVIGATOR.standalone !== UNDEF_TYPE && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) { - this.set(MODEL, 'iPad') - .set(TYPE, TABLET); - } - } - UAParserDevice.prototype = new UAParserItem(); - UAParserDevice.prototype.parseCH = function () { - if (this.uaCH[MOBILE]) { - this.set(TYPE, MOBILE); - } - if (this.uaCH[MODEL]) { - this.set(MODEL, this.uaCH[MODEL]); + UAParserItem.prototype.parseCH = function () { + var ua = this.ua, + uaCH = this.uaCH, + rgxMap = this.rgxMap; + + switch (this.itemType) { + case UA_BROWSER: + var brands = uaCH[FULLVERLIST] || uaCH[BRANDS]; + if (brands) { + for (var i in brands) { + var brandName = brands[i].brand, + brandVersion = brands[i].version; + if (!/not.a.brand/i.test(brandName) || /chromi/i.test(this.get(NAME))) { + this.set(NAME, strip(GOOGLE+' ', brandName)) + .set(VERSION, brandVersion) + .set(MAJOR, majorize(brandVersion)); + } + } + } + break; + case UA_CPU: + var archName = uaCH[ARCHITECTURE]; + if (archName) { + archName += (archName && uaCH[BITNESS] == '64') ? '64' : EMPTY; + rgxMapper.call(this.data, archName, rgxMap[this.itemType]); + } + break; + case UA_DEVICE: + if (uaCH[MOBILE]) { + this.set(TYPE, MOBILE); + } + if (uaCH[MODEL]) { + this.set(MODEL, uaCH[MODEL]); + } + break; + case UA_OS: + var osName = uaCH[PLATFORM]; + if(osName) { + var osVersion = uaCH[PLATFORMVER]; + osVersion = (osName == WINDOWS) ? (parseInt(majorize(osVersion), 10) >= 13 ? '11' : '10') : osVersion; + this.set(NAME, osName) + .set(VERSION, osVersion); + } + break; + case UA_RESULT: + var createUAParserItemWithCH = function (itemType) { + return new UAParserItem(itemType, ua, rgxMap, uaCH).parseCH().get(); + }; + this.set('ua', ua) + .set('ua_ch', uaCH) + .set(UA_BROWSER, createUAParserItemWithCH(UA_BROWSER)) + .set(UA_CPU, createUAParserItemWithCH(UA_CPU)) + .set(UA_DEVICE, createUAParserItemWithCH(UA_DEVICE)) + .set(UA_ENGINE, createUAParserItemWithCH(UA_ENGINE)) + .set(UA_OS, createUAParserItemWithCH(UA_OS)); } return this; }; - - function UAParserEngine (ua, engineMap) { - UAParserItem.call(this, [ - ua, - null, - UA_ENGINE, - engineMap, - [NAME, VERSION], - [VERSION], - null, - [NAME, VERSION] - ]); - this.parse(); - } - UAParserEngine.prototype = new UAParserItem(); - - function UAParserOS (ua, osMap, uach) { - UAParserItem.call(this, [ - ua, - uach, - UA_OS, - osMap, - [NAME, VERSION], - [VERSION], - ' ?os$', - [NAME, VERSION] - ]); - this.parse(); - if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[PLATFORM] && NAVIGATOR_UADATA[PLATFORM] != 'Unknown') { - this.set(NAME, NAVIGATOR_UADATA[PLATFORM]); - } - } - UAParserOS.prototype = new UAParserItem(); - UAParserOS.prototype.parseCH = function () { - var osName = this.uaCH[PLATFORM]; - if(osName) { - var osVersion = this.uaCH[PLATFORMVER]; - osVersion = (osName == WINDOWS) ? (parseInt(majorize(osVersion), 10) >= 13 ? '11' : '10') : osVersion; - this.set(NAME, osName) - .set(VERSION, osVersion); - } + UAParserItem.prototype.set = function (prop, val) { + this.data[prop] = val; return this; }; - function UAParserResult (ua, resMap, uach) { - UAParserItem.call(this, [ua, uach, UA_RESULT, resMap]); - } - UAParserResult.prototype = new UAParserItem(); - function UAParser (ua, extensions, headers) { if (typeof ua === OBJ_TYPE) { @@ -1162,52 +1123,29 @@ HTTP_UACH = new UAParserDataCH(headers, true), regexMap = extensions ? - extend(regexes, extensions) : - regexes; + extend(defaultRegexes, extensions) : + defaultRegexes, - // public methods - this.getBrowser = function () { - return new UAParserBrowser(userAgent, regexMap[UA_BROWSER], HTTP_UACH).get(); - }; - - this.getCPU = function () { - return new UAParserCPU(userAgent, regexMap[UA_CPU], HTTP_UACH).get(); - }; - - this.getDevice = function () { - return new UAParserDevice(userAgent, regexMap[UA_DEVICE], HTTP_UACH).get(); - }; - - this.getEngine = function () { - return new UAParserEngine(userAgent, regexMap[UA_ENGINE]).get(); - }; - - this.getOS = function () { - return new UAParserOS(userAgent, regexMap[UA_OS], HTTP_UACH).get(); - }; - - this.getResult = function () { - return new UAParserResult(userAgent, regexMap, HTTP_UACH) - .set('ua', userAgent) - .set('ua_ch', HTTP_UACH) - .set(UA_BROWSER, this.getBrowser()) - .set(UA_CPU, this.getCPU()) - .set(UA_DEVICE, this.getDevice()) - .set(UA_ENGINE, this.getEngine()) - .set(UA_OS, this.getOS()) - .get(); - }; - - this.getUA = function () { - return userAgent; - }; - - this.setUA = function (ua) { - userAgent = (typeof ua === STR_TYPE && ua.length > UA_MAX_LENGTH) ? trim(ua, UA_MAX_LENGTH) : ua; - return this; - }; + createUAParserItemFunc = function (itemType) { + return function () { + return new UAParserItem(itemType, userAgent, regexMap, HTTP_UACH).get(); + }; + }; - this.setUA(userAgent); + // public methods + assignFromEntries.call(this, [ + ['getBrowser', createUAParserItemFunc(UA_BROWSER)], + ['getCPU', createUAParserItemFunc(UA_CPU)], + ['getDevice', createUAParserItemFunc(UA_DEVICE)], + ['getEngine', createUAParserItemFunc(UA_ENGINE)], + ['getOS', createUAParserItemFunc(UA_OS)], + ['getResult', createUAParserItemFunc(UA_RESULT)], + ['getUA', function () { return userAgent; }], + ['setUA', function (ua) { + userAgent = (typeof ua === STR_TYPE && ua.length > UA_MAX_LENGTH) ? trim(ua, UA_MAX_LENGTH) : ua; + return this; + }] + ]).setUA(userAgent); return this; } diff --git a/test/browser-test.json b/test/browser-test.json index ef0cb34f..20e7d54f 100644 --- a/test/browser-test.json +++ b/test/browser-test.json @@ -753,7 +753,7 @@ "ua" : "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7", "expect" : { - "name" : "Mobile Safari", + "name" : "Safari Mobile", "version" : "4.0.5", "major" : "4" } @@ -1393,7 +1393,7 @@ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) FxiOS/1.1 Mobile/13B143 Safari/601.1.46", "expect" : { - "name" : "Firefox", + "name" : "Firefox Mobile", "version" : "1.1", "major" : "1" } @@ -1403,7 +1403,7 @@ "ua" : "Mozilla/5.0 (iPad; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4", "expect" : { - "name" : "Firefox", + "name" : "Firefox Mobile", "version" : "1.0", "major" : "1" } @@ -1597,7 +1597,7 @@ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 15_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6,2 Mobile/15E148 Safari/604.1", "expect" : { - "name" : "Mobile Safari", + "name" : "Safari Mobile", "version" : "15.6,2", "major" : "15" } @@ -1682,5 +1682,35 @@ "version": "10.25.0", "major" : "10" } + }, + { + "desc" : "Chrome Mobile", + "ua" : "Mozilla/5.0 (Linux; Android 7.1.2; Nexus 5X Build/N2G47W) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "expect" : + { + "name" : "Chrome Mobile", + "version" : "58.0.3029.83", + "major" : "58" + } + }, + { + "desc" : "Firefox Mobile", + "ua" : "Mozilla/5.0 (Linux; Android 7.1.2; Nexus 5X Build/N2G47W) AppleWebKit/537.36 (KHTML, like Gecko) FxiOS/7.5b3349 Mobile/14F89 Safari/603.2.4", + "expect" : + { + "name" : "Firefox Mobile", + "version" : "7.5b3349", + "major" : "7" + } + }, + { + "desc" : "Firefox Mobile", + "ua" : "Mozilla/5.0 (Android 5.0; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0", + "expect" : + { + "name" : "Firefox Mobile", + "version" : "41.0", + "major" : "41" + } } -] +] \ No newline at end of file diff --git a/test/es6-test.mjs b/test/es6-test.mjs new file mode 100644 index 00000000..4d849587 --- /dev/null +++ b/test/es6-test.mjs @@ -0,0 +1,17 @@ +import { UAParser } from '../dist/ua-parser.mjs' +import * as assert from 'assert' + +describe('Returns', () => { + it('getResult() should returns object', () => { + assert.deepEqual(new UAParser('').getResult(), + { + ua : '', + ua_ch : { architecture: undefined, bitness: undefined, brands: undefined, fullVersionList: undefined, mobile: false, model: undefined, platform: undefined, platformVersion: undefined }, + browser: { name: undefined, version: undefined, major: undefined }, + cpu: { architecture: undefined }, + device: { vendor: undefined, model: undefined, type: undefined }, + engine: { name: undefined, version: undefined}, + os: { name: undefined, version: undefined } + }); + }); +}); \ No newline at end of file diff --git a/test/test.js b/test/test.js index 39e948e1..c744aa51 100644 --- a/test/test.js +++ b/test/test.js @@ -195,6 +195,7 @@ describe('is() utility method', function () { assert.strictEqual(uap.getBrowser().name, "IEMobile"); assert.strictEqual(uap.getBrowser().is("IEMobile"), true); assert.strictEqual(uap.getBrowser().is("IE"), false); + assert.strictEqual(uap.getBrowser().is("11.0"), false); }); it('Should ignore "Browser" suffix', function () { @@ -204,6 +205,7 @@ describe('is() utility method', function () { it('Should ignore case', function () { assert.strictEqual(uap.getEngine().name, "Trident"); assert.strictEqual(uap.getEngine().is("tRiDeNt"), true); + assert.strictEqual(uap.getEngine().is("7.0"), false); }); it('Should get exact name', function () {