Expose UA-CH data in getResult()

This commit is contained in:
Faisal Salman 2023-03-09 23:51:23 +07:00
parent 3c3c03ceeb
commit 5672a2e15c
2 changed files with 110 additions and 76 deletions

View File

@ -95,20 +95,34 @@
has = function (str1, str2) { has = function (str1, str2) {
return typeof str1 === STR_TYPE ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false; 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;
}
}
return this;
},
isExtensions = function (obj) { isExtensions = function (obj) {
for (var prop in obj) { for (var prop in obj) {
return /^(browser|cpu|device|engine|os)$/.test(prop); return /^(browser|cpu|device|engine|os)$/.test(prop);
} }
}, },
lowerize = function (str, rgx) { lowerize = function (str, rgx) {
return typeof(str) === STR_TYPE ? str.toLowerCase().replace((rgx ? new RegExp(rgx, 'i') : EMPTY), EMPTY) : str; return typeof(str) === STR_TYPE ? strip((rgx ? new RegExp(rgx, 'i') : EMPTY), str.toLowerCase()) : str;
}, },
majorize = function (version) { majorize = function (version) {
return typeof(version) === STR_TYPE ? version.replace(/[^\d\.]/g, EMPTY).split('.')[0] : undefined; return typeof(version) === STR_TYPE ? strip(/[^\d\.]/g, version).split('.')[0] : undefined;
},
strip = function (pattern, str) {
return str.replace(pattern, EMPTY);
}, },
trim = function (str, len) { trim = function (str, len) {
if (typeof(str) === STR_TYPE) { if (typeof(str) === STR_TYPE) {
str = str.replace(/^\s\s*/, EMPTY); str = strip(/^\s\s*/, str);
return typeof(len) === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH); return typeof(len) === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH);
} }
}; };
@ -799,20 +813,45 @@
// Constructor // Constructor
//////////////// ////////////////
function UACHData (uach, isBrowser) {
uach = uach || {};
if (!isBrowser) {
initialize.call(this, ['brands', MOBILE, MODEL, 'platform', 'platformVersion', ARCHITECTURE, 'bitness']);
if ((uach[CH_HEADER_FULL_VER_LIST] || uach[CH_HEADER])) {
this.brands = [];
var tokens = strip(/\\?\"/g, (uach[CH_HEADER_FULL_VER_LIST] || uach[CH_HEADER])).split(', ');
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i].split(';v=');
this.brands[i] = { brand : token[0], version : token[1] };
}
}
this.mobile = uach[CH_HEADER_MOBILE] == '?1';
var setHeader = function (header) {
return header ? strip(/\"/g, header) : undefined;
};
this.model = setHeader(uach[CH_HEADER_MODEL]);
this.platform = setHeader(uach[CH_HEADER_PLATFORM]);
this.platformVersion = setHeader(uach[CH_HEADER_PLATFORM_VER]);
this.architecture = setHeader(uach[CH_HEADER_ARCH]);
this.bitness = setHeader(uach[CH_HEADER_BITNESS]);
}
return this;
}
function UAItem (data) { function UAItem (data) {
if (!data) return; if (!data) return;
this.ua = data[0]; this.ua = data[0];
this.rgxMap = data[1]; this.rgxMap = data[1];
this.uaCH = data[2];
this.data = (function (data) { this.data = (function (data) {
var is_ignoreProps = data[3], var init_props = data[3],
is_ignoreRgx = data[4], is_ignoreProps = data[4],
toString_props = data[5]; is_ignoreRgx = data[5],
toString_props = data[6];
var UAData = function () { function UAData () {
for (var init_props in data[2]) { initialize.call(this, init_props);
this[data[2][init_props]] = undefined; }
}
};
UAData.prototype.is = function (strToCheck) { UAData.prototype.is = function (strToCheck) {
var is = false; var is = false;
for (var i in this) { for (var i in this) {
@ -851,10 +890,11 @@
return this; return this;
}; };
function UABrowser (ua, browserMap) { function UABrowser (ua, browserMap, uach) {
UAItem.call(this, [ UAItem.call(this, [
ua, ua,
browserMap, browserMap,
uach,
[NAME, VERSION, MAJOR], [NAME, VERSION, MAJOR],
[VERSION, MAJOR], [VERSION, MAJOR],
' ?browser$', ' ?browser$',
@ -862,31 +902,27 @@
]); ]);
} }
UABrowser.prototype = new UAItem(); UABrowser.prototype = new UAItem();
UABrowser.prototype.parse = function (uach) { UABrowser.prototype.parseCH = function () {
if (uach) { var brands = this.uaCH.brands;
var brands = uach[CH_HEADER_FULL_VER_LIST] || uach[CH_HEADER]; if (brands) {
if (brands) { for (var i in brands) {
brands = brands.replace(/\\?\"/g, EMPTY).split(', '); var brandName = brands[i].brand,
for (var i in brands) { brandVersion = brands[i].version;
var brand = brands[i].split(';v='), if (!/not.a.brand/i.test(brandName) && (!this.get(NAME) || /chromi/i.test(this.get(NAME)))) {
brandName = brand[0], this.set(NAME, strip(GOOGLE+' ', brandName))
brandVersion = brand[1]; .set(VERSION, brandVersion)
if (!/not.a.brand/i.test(brandName) && (!this.get(NAME) || /chromi/i.test(this.get(NAME)))) { .set(MAJOR, majorize(brandVersion));
this.set(NAME, brandName.replace(GOOGLE+' ', EMPTY))
.set(VERSION, brandVersion)
.set(MAJOR, majorize(brandVersion));
}
} }
} }
} }
if (!this.get(NAME)) UAItem.prototype.parse.call(this);
return this; return this;
}; };
function UACPU (ua, cpuMap) { function UACPU (ua, cpuMap, uach) {
UAItem.call(this, [ UAItem.call(this, [
ua, ua,
cpuMap, cpuMap,
uach,
[ARCHITECTURE], [ARCHITECTURE],
[], [],
null, null,
@ -894,20 +930,18 @@
]); ]);
} }
UACPU.prototype = new UAItem(); UACPU.prototype = new UAItem();
UACPU.prototype.parse = function (uach) { UACPU.prototype.parseCH = function () {
if (uach) { var archName = this.uaCH.architecture;
var archName = uach[CH_HEADER_ARCH]; archName += (archName && this.uaCH.bitness == '64') ? '64' : EMPTY;
archName += (archName && uach[CH_HEADER_BITNESS] == '64') ? '64' : EMPTY; rgxMapper.call(this.data, archName, this.rgxMap);
rgxMapper.call(this.data, archName, this.rgxMap);
}
if (!this.get(ARCHITECTURE)) UAItem.prototype.parse.call(this);
return this; return this;
}; };
function UADevice (ua, deviceMap) { function UADevice (ua, deviceMap, uach) {
UAItem.call(this, [ UAItem.call(this, [
ua, ua,
deviceMap, deviceMap,
uach,
[TYPE, MODEL, VENDOR], [TYPE, MODEL, VENDOR],
[], [],
null, null,
@ -915,16 +949,13 @@
]); ]);
} }
UADevice.prototype = new UAItem(); UADevice.prototype = new UAItem();
UADevice.prototype.parse = function (uach) { UADevice.prototype.parseCH = function () {
if (uach) { if (this.uaCH.mobile) {
this.set(TYPE, uach[CH_HEADER_MOBILE] == '?1' ? this.set(TYPE, MOBILE);
MOBILE : }
this.get(TYPE)) if (this.uaCH.model) {
.set(MODEL, uach[CH_HEADER_MODEL] ? this.set(MODEL, strip(/\"/g, this.uaCH.model));
uach[CH_HEADER_MODEL].replace(/\"/g, EMPTY) :
this.get(MODEL));
} }
if (!this.get(TYPE) && !this.get(MODEL)) UAItem.prototype.parse.call(this);
return this; return this;
}; };
@ -932,6 +963,7 @@
UAItem.call(this, [ UAItem.call(this, [
ua, ua,
engineMap, engineMap,
null,
[NAME, VERSION], [NAME, VERSION],
[VERSION], [VERSION],
null, null,
@ -940,10 +972,11 @@
} }
UAEngine.prototype = new UAItem(); UAEngine.prototype = new UAItem();
function UAOS (ua, osMap) { function UAOS (ua, osMap, uach) {
UAItem.call(this, [ UAItem.call(this, [
ua, ua,
osMap, osMap,
uach,
[NAME, VERSION], [NAME, VERSION],
[VERSION], [VERSION],
' ?os$', ' ?os$',
@ -951,22 +984,15 @@
]); ]);
} }
UAOS.prototype = new UAItem(); UAOS.prototype = new UAItem();
UAOS.prototype.parse = function (uach) { UAOS.prototype.parseCH = function (uach) {
if (uach) { var osName = this.uaCH.platform;
var osName = uach[CH_HEADER_PLATFORM] ? uach[CH_HEADER_PLATFORM].replace(/\"/g, EMPTY) : undefined; var osVersion = this.uaCH.platformVersion;
var osVersion = uach[CH_HEADER_PLATFORM_VER] ? uach[CH_HEADER_PLATFORM_VER].replace(/\"/g, EMPTY) : undefined; osVersion = (osName == WINDOWS) ? (parseInt(majorize(osVersion), 10) >= 13 ? '11' : '10') : osVersion;
osVersion = (osName == WINDOWS) ? (majorize(osVersion) >= 13 ? '11' : '10') : osVersion; this.set(NAME, osName)
this.set(NAME, osName) .set(VERSION, osVersion);
.set(VERSION, osVersion);
}
if (!this.get(NAME)) UAItem.prototype.parse.call(this);
if (/(chrome |mac)os/.test(this.get(NAME))) {
this.set(NAME, this.get(NAME)
.replace(/chrome os/i, CHROMIUM_OS)
.replace(/macos/i, MAC_OS));
}
return this; return this;
}; };
function UAParser (ua, extensions, headers) { function UAParser (ua, extensions, headers) {
@ -1000,8 +1026,10 @@
(headers && headers[USER_AGENT] ? (headers && headers[USER_AGENT] ?
headers[USER_AGENT] : headers[USER_AGENT] :
EMPTY)), EMPTY)),
clientHints = new UACHData(headers, false),
clientHints = (navigator && navigator.userAgentData) ? userAgentData = (navigator && navigator.userAgentData) ?
navigator.userAgentData : navigator.userAgentData :
undefined, undefined,
@ -1011,10 +1039,11 @@
// public methods // public methods
this.getBrowser = function () { this.getBrowser = function () {
var browser = new UABrowser(userAgent, regexMap.browser); var browser = new UABrowser(userAgent, regexMap.browser, clientHints);
if (headers && (headers[CH_HEADER_FULL_VER_LIST] || headers[CH_HEADER])) { if (headers && (headers[CH_HEADER_FULL_VER_LIST] || headers[CH_HEADER])) {
browser.parse(headers); browser.parseCH();
} else { }
if (!browser.get(NAME)) {
browser.parse(); browser.parse();
// Brave-specific detection // Brave-specific detection
if (navigator && navigator.brave && typeof navigator.brave.isBrave == FUNC_TYPE) { if (navigator && navigator.brave && typeof navigator.brave.isBrave == FUNC_TYPE) {
@ -1027,22 +1056,24 @@
}; };
this.getCPU = function () { this.getCPU = function () {
var cpu = new UACPU(userAgent, regexMap.cpu); var cpu = new UACPU(userAgent, regexMap.cpu, clientHints);
if (headers && headers[CH_HEADER_ARCH]) { if (headers && headers[CH_HEADER_ARCH]) {
cpu.parse(headers); cpu.parseCH();
} else { }
if (!cpu.get(ARCHITECTURE)) {
cpu.parse(); cpu.parse();
} }
return cpu.get(); return cpu.get();
}; };
this.getDevice = function () { this.getDevice = function () {
var device = new UADevice(userAgent, regexMap.device); var device = new UADevice(userAgent, regexMap.device, clientHints);
if (headers && (headers[CH_HEADER_MOBILE] || headers[CH_HEADER_MODEL])) { if (headers && (headers[CH_HEADER_MOBILE] || headers[CH_HEADER_MODEL])) {
device.parse(headers); device.parseCH();
} else { }
if (!device.get(TYPE) || !device.get(MODEL)) {
device.parse(); device.parse();
if (!device.get(TYPE) && clientHints && clientHints.mobile) { if (!device.get(TYPE) && userAgentData && userAgentData.mobile) {
device.set(TYPE, MOBILE); device.set(TYPE, MOBILE);
} }
// iPadOS-specific detection: identified as Mac, but has some iOS-only properties // iPadOS-specific detection: identified as Mac, but has some iOS-only properties
@ -1062,13 +1093,14 @@
}; };
this.getOS = function () { this.getOS = function () {
var os = new UAOS(userAgent, regexMap.os); var os = new UAOS(userAgent, regexMap.os, clientHints);
if (headers && headers[CH_HEADER_PLATFORM]) { if (headers && headers[CH_HEADER_PLATFORM]) {
os.parse(headers); os.parseCH();
} else { }
if (!os.get(NAME)) {
os.parse(); os.parse();
if (!os.get(NAME) && clientHints && clientHints.platform != 'Unknown') { if (!os.get(NAME) && userAgentData && userAgentData.platform != 'Unknown') {
os.set(NAME, clientHints.platform os.set(NAME, userAgentData.platform
.replace(/chrome os/i, CHROMIUM_OS) .replace(/chrome os/i, CHROMIUM_OS)
.replace(/macos/i, MAC_OS)); // backward compatibility .replace(/macos/i, MAC_OS)); // backward compatibility
} }
@ -1079,6 +1111,7 @@
this.getResult = function () { this.getResult = function () {
return { return {
'ua' : userAgent, 'ua' : userAgent,
'ua_ch' : clientHints,
'browser' : this.getBrowser(), 'browser' : this.getBrowser(),
'cpu' : this.getCPU(), 'cpu' : this.getCPU(),
'device' : this.getDevice(), 'device' : this.getDevice(),

View File

@ -82,6 +82,7 @@ describe('Returns', function () {
assert.deepEqual(new UAParser('').getResult(), assert.deepEqual(new UAParser('').getResult(),
{ {
ua : '', ua : '',
ua_ch : { architecture: undefined, bitness: undefined, brands: undefined, mobile: false, model: undefined, platform: undefined, platformVersion: undefined },
browser: { name: undefined, version: undefined, major: undefined }, browser: { name: undefined, version: undefined, major: undefined },
cpu: { architecture: undefined }, cpu: { architecture: undefined },
device: { vendor: undefined, model: undefined, type: undefined }, device: { vendor: undefined, model: undefined, type: undefined },