Small refactor

This commit is contained in:
Faisal Salman 2023-03-20 01:17:04 +07:00
parent 08436ce4f5
commit 6b3fc3e0f1
4 changed files with 261 additions and 308 deletions

View File

@ -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"

View File

@ -95,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];
@ -110,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) {
@ -138,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;
@ -270,7 +276,7 @@
// Regex map
/////////////
var regexes = {
var defaultRegexes = {
browser : [[
@ -845,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])],
@ -872,123 +971,119 @@
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;
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();
};
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;
};
}
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);
if (this.itemType != UA_RESULT) {
rgxMapper.call(this.data, this.ua, this.rgxMap[this.itemType]);
}
return this;
};
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;
};
UAParserItem.prototype.set = function (prop, val) {
@ -996,144 +1091,6 @@
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));
}
}
}
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]);
}
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);
}
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) {
@ -1166,52 +1123,29 @@
HTTP_UACH = new UAParserDataCH(headers, true),
regexMap = extensions ?
extend(regexes, extensions) :
regexes;
extend(defaultRegexes, extensions) :
defaultRegexes,
createUAParserItemFunc = function (itemType) {
return function () {
return new UAParserItem(itemType, userAgent, regexMap, HTTP_UACH).get();
};
};
// 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;
};
this.setUA(userAgent);
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;
}

17
test/es6-test.mjs Normal file
View File

@ -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 }
});
});
});

View File

@ -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 () {