mirror of
https://github.com/faisalman/ua-parser-js.git
synced 2025-09-27 16:08:47 +03:00
Only use user-agent data by default. Must explicitly call withClientHints()
to also use client-hints data
This commit is contained in:
parent
60d3a2fbbc
commit
f8dde65d54
133
src/ua-parser.js
133
src/ua-parser.js
@ -39,6 +39,11 @@
|
||||
EMBEDDED = 'embedded',
|
||||
USER_AGENT = 'user-agent',
|
||||
UA_MAX_LENGTH = 350,
|
||||
BRANDS = 'brands',
|
||||
FULLVERLIST = 'fullVersionList',
|
||||
PLATFORM = 'platform',
|
||||
PLATFORMVER = 'platformVersion',
|
||||
BITNESS = 'bitness',
|
||||
CH_HEADER = 'sec-ch-ua',
|
||||
CH_HEADER_FULL_VER_LIST = CH_HEADER + '-full-version-list',
|
||||
CH_HEADER_ARCH = CH_HEADER + '-arch',
|
||||
@ -127,6 +132,16 @@
|
||||
return /^(browser|cpu|device|engine|os)$/.test(prop);
|
||||
}
|
||||
},
|
||||
itemListToArray = function (header) {
|
||||
if (!header) return undefined;
|
||||
var arr = [];
|
||||
var tokens = strip(/\\?\"/g, header).split(', ');
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i].split(';v=');
|
||||
arr[i] = { brand : token[0], version : token[1] };
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
lowerize = function (str, rgx) {
|
||||
return typeof(str) === STR_TYPE ? strip((rgx ? new RegExp(rgx, 'i') : EMPTY), str.toLowerCase()) : str;
|
||||
},
|
||||
@ -136,6 +151,9 @@
|
||||
strip = function (pattern, str) {
|
||||
return str.replace(pattern, EMPTY);
|
||||
},
|
||||
stripQuotes = function (val) {
|
||||
return typeof val === STR_TYPE ? strip(/\"/g, val) : val;
|
||||
},
|
||||
trim = function (str, len) {
|
||||
if (typeof(str) === STR_TYPE) {
|
||||
str = strip(/^\s\s*/, str);
|
||||
@ -830,31 +848,21 @@
|
||||
function UAParserDataCH (uach, isHTTP_UACH) {
|
||||
uach = uach || {};
|
||||
initialize.call(this, CH_ALL_VALUES);
|
||||
var setVal = function (val) {
|
||||
return typeof val === STR_TYPE ? strip(/\"/g, val) : val || undefined;
|
||||
};
|
||||
if (isHTTP_UACH) {
|
||||
var toArray = function (header) {
|
||||
if (!header) return undefined;
|
||||
var arr = [];
|
||||
var tokens = strip(/\\?\"/g, header).split(', ');
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var token = tokens[i].split(';v=');
|
||||
arr[i] = { brand : token[0], version : token[1] };
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
this.brands = toArray(uach[CH_HEADER]);
|
||||
this.fullVersionList = toArray(uach[CH_HEADER_FULL_VER_LIST]);
|
||||
this.mobile = /\?1/.test(uach[CH_HEADER_MOBILE]);
|
||||
this.model = setVal(uach[CH_HEADER_MODEL]);
|
||||
this.platform = setVal(uach[CH_HEADER_PLATFORM]);
|
||||
this.platformVersion = setVal(uach[CH_HEADER_PLATFORM_VER]);
|
||||
this.architecture = setVal(uach[CH_HEADER_ARCH]);
|
||||
this.bitness = setVal(uach[CH_HEADER_BITNESS]);
|
||||
initialize.call(this, [
|
||||
[BRANDS, itemListToArray(uach[CH_HEADER])],
|
||||
[FULLVERLIST, itemListToArray(uach[CH_HEADER_FULL_VER_LIST])],
|
||||
[BRANDS, itemListToArray(uach[CH_HEADER])],
|
||||
[MOBILE, /\?1/.test(uach[CH_HEADER_MOBILE])],
|
||||
[MODEL, stripQuotes(uach[CH_HEADER_MODEL])],
|
||||
[PLATFORM, stripQuotes(uach[CH_HEADER_PLATFORM])],
|
||||
[PLATFORMVER, stripQuotes(uach[CH_HEADER_PLATFORM_VER])],
|
||||
[ARCHITECTURE, stripQuotes(uach[CH_HEADER_ARCH])],
|
||||
[BITNESS, stripQuotes(uach[CH_HEADER_BITNESS])]
|
||||
]);
|
||||
} else {
|
||||
for (var prop in uach) {
|
||||
if(this.hasOwnProperty(prop) && uach[prop]) this[prop] = setVal(uach[prop]);
|
||||
if(this.hasOwnProperty(prop) && uach[prop]) this[prop] = stripQuotes(uach[prop]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@ -867,6 +875,7 @@
|
||||
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],
|
||||
@ -900,17 +909,41 @@
|
||||
return str ? str : UNDEF_TYPE;
|
||||
};
|
||||
UAParserData.prototype.withClientHints = function () {
|
||||
if (!NAVIGATOR_UADATA) return this;
|
||||
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 {
|
||||
'ua' : ua,
|
||||
'ua_ch' : uaCH,
|
||||
'browser' : new UAParserBrowser(ua, rgxMap[UA_BROWSER], HTTP_UACH).parseCH().get(),
|
||||
'cpu' : new UAParserCPU(ua, rgxMap[UA_CPU], HTTP_UACH).parseCH().get(),
|
||||
'device' : new UAParserDevice(ua, rgxMap[UA_DEVICE], HTTP_UACH).parseCH().get(),
|
||||
'engine' : new UAParserEngine(ua, rgxMap[UA_ENGINE]).get(),
|
||||
'os' : new UAParserOS(ua, rgxMap[UA_OS], HTTP_UACH).parseCH().get()
|
||||
};
|
||||
}
|
||||
}
|
||||
return NAVIGATOR_UADATA
|
||||
.getHighEntropyValues(CH_ALL_VALUES)
|
||||
.then(function (res) {
|
||||
|
||||
var JS_UACH = new UAParserDataCH(res, false),
|
||||
browser = new UAParserBrowser(ua, rgxMap, JS_UACH).get(),
|
||||
cpu = new UAParserCPU(ua, ((itemType == UA_RESULT) ? rgxMap.cpu : rgxMap), JS_UACH).get(),
|
||||
device = new UAParserDevice(ua, rgxMap, JS_UACH).get(),
|
||||
browser = new UAParserBrowser(ua, rgxMap, JS_UACH).parseCH().get(),
|
||||
cpu = new UAParserCPU(ua, ((itemType == UA_RESULT) ? rgxMap[UA_CPU] : rgxMap), JS_UACH).parseCH().get(),
|
||||
device = new UAParserDevice(ua, rgxMap, JS_UACH).parseCH().get(),
|
||||
engine = new UAParserEngine(ua, rgxMap).get(),
|
||||
os = new UAParserOS(ua, rgxMap, JS_UACH).get();
|
||||
os = new UAParserOS(ua, rgxMap, JS_UACH).parseCH().get();
|
||||
|
||||
switch (itemType) {
|
||||
case UA_BROWSER:
|
||||
@ -963,24 +996,21 @@
|
||||
' ?browser$',
|
||||
[NAME, VERSION]
|
||||
]);
|
||||
this.parseCH();
|
||||
if (!this.get(NAME)) {
|
||||
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.fullVersionList || this.uaCH.brands;
|
||||
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) && (!this.get(NAME) || /chromi/i.test(this.get(NAME)))) {
|
||||
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));
|
||||
@ -1001,16 +1031,13 @@
|
||||
null,
|
||||
[ARCHITECTURE]
|
||||
]);
|
||||
this.parseCH();
|
||||
if (!this.get(ARCHITECTURE)) {
|
||||
this.parse();
|
||||
}
|
||||
}
|
||||
UAParserCPU.prototype = new UAParserItem();
|
||||
UAParserCPU.prototype.parseCH = function () {
|
||||
var archName = this.uaCH.architecture;
|
||||
var archName = this.uaCH[ARCHITECTURE];
|
||||
if (archName) {
|
||||
archName += (archName && this.uaCH.bitness == '64') ? '64' : EMPTY;
|
||||
archName += (archName && this.uaCH[BITNESS] == '64') ? '64' : EMPTY;
|
||||
rgxMapper.call(this.data, archName, this.rgxMap);
|
||||
}
|
||||
return this;
|
||||
@ -1027,10 +1054,8 @@
|
||||
null,
|
||||
[VENDOR, MODEL]
|
||||
]);
|
||||
this.parseCH();
|
||||
if (!this.get(TYPE) || !this.get(MODEL)) {
|
||||
this.parse();
|
||||
if (!this.get(TYPE) && NAVIGATOR_UADATA && NAVIGATOR_UADATA.mobile) {
|
||||
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
|
||||
@ -1039,14 +1064,13 @@
|
||||
.set(TYPE, TABLET);
|
||||
}
|
||||
}
|
||||
}
|
||||
UAParserDevice.prototype = new UAParserItem();
|
||||
UAParserDevice.prototype.parseCH = function () {
|
||||
if (this.uaCH.mobile) {
|
||||
if (this.uaCH[MOBILE]) {
|
||||
this.set(TYPE, MOBILE);
|
||||
}
|
||||
if (this.uaCH.model) {
|
||||
this.set(MODEL, strip(/\"/g, this.uaCH.model));
|
||||
if (this.uaCH[MODEL]) {
|
||||
this.set(MODEL, this.uaCH[MODEL]);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
@ -1077,21 +1101,18 @@
|
||||
' ?os$',
|
||||
[NAME, VERSION]
|
||||
]);
|
||||
this.parseCH();
|
||||
if (!this.get(NAME)) {
|
||||
this.parse();
|
||||
if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA.platform && NAVIGATOR_UADATA.platform != 'Unknown') {
|
||||
this.set(NAME, NAVIGATOR_UADATA.platform
|
||||
if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[PLATFORM] && NAVIGATOR_UADATA[PLATFORM] != 'Unknown') {
|
||||
this.set(NAME, NAVIGATOR_UADATA[PLATFORM]
|
||||
.replace(/chrome os/i, CHROMIUM_OS)
|
||||
.replace(/macos/i, MAC_OS)); // backward compatibility
|
||||
}
|
||||
}
|
||||
}
|
||||
UAParserOS.prototype = new UAParserItem();
|
||||
UAParserOS.prototype.parseCH = function () {
|
||||
var osName = this.uaCH.platform;
|
||||
var osName = this.uaCH[PLATFORM];
|
||||
if(osName) {
|
||||
var osVersion = this.uaCH.platformVersion;
|
||||
var osVersion = this.uaCH[PLATFORMVER];
|
||||
osVersion = (osName == WINDOWS) ? (parseInt(majorize(osVersion), 10) >= 13 ? '11' : '10') : osVersion;
|
||||
this.set(NAME, osName)
|
||||
.set(VERSION, osVersion);
|
||||
@ -1141,23 +1162,23 @@
|
||||
|
||||
// public methods
|
||||
this.getBrowser = function () {
|
||||
return new UAParserBrowser(userAgent, regexMap.browser, HTTP_UACH).get();
|
||||
return new UAParserBrowser(userAgent, regexMap[UA_BROWSER], HTTP_UACH).get();
|
||||
};
|
||||
|
||||
this.getCPU = function () {
|
||||
return new UAParserCPU(userAgent, regexMap.cpu, HTTP_UACH).get();
|
||||
return new UAParserCPU(userAgent, regexMap[UA_CPU], HTTP_UACH).get();
|
||||
};
|
||||
|
||||
this.getDevice = function () {
|
||||
return new UAParserDevice(userAgent, regexMap.device, HTTP_UACH).get();
|
||||
return new UAParserDevice(userAgent, regexMap[UA_DEVICE], HTTP_UACH).get();
|
||||
};
|
||||
|
||||
this.getEngine = function () {
|
||||
return new UAParserEngine(userAgent, regexMap.engine).get();
|
||||
return new UAParserEngine(userAgent, regexMap[UA_ENGINE]).get();
|
||||
};
|
||||
|
||||
this.getOS = function () {
|
||||
return new UAParserOS(userAgent, regexMap.os, HTTP_UACH).get();
|
||||
return new UAParserOS(userAgent, regexMap[UA_OS], HTTP_UACH).get();
|
||||
};
|
||||
|
||||
this.getResult = function () {
|
||||
|
166
test/test.js
166
test/test.js
@ -352,53 +352,133 @@ describe('Map UA-CH headers', function () {
|
||||
'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
|
||||
};
|
||||
|
||||
let uap = UAParser(headers).withClientHints();
|
||||
let browser = new UAParser(headers).getBrowser().withClientHints();
|
||||
let cpu = new UAParser(headers).getCPU().withClientHints();
|
||||
let device = new UAParser(headers).getDevice().withClientHints();
|
||||
let engine = new UAParser(headers).getEngine().withClientHints();
|
||||
let os = new UAParser(headers).getOS().withClientHints();
|
||||
|
||||
let ua_ch = {
|
||||
"architecture": "ARM",
|
||||
"bitness": "64",
|
||||
"brands": [
|
||||
{
|
||||
"brand": "Chromium",
|
||||
"version": "93"
|
||||
},
|
||||
{
|
||||
"brand": "Google Chrome",
|
||||
"version": "93"
|
||||
},
|
||||
{
|
||||
"brand": " Not;A Brand",
|
||||
"version": "99"
|
||||
}
|
||||
],
|
||||
"fullVersionList": [
|
||||
{
|
||||
"brand": "Chromium",
|
||||
"version": "93.0.1.2"
|
||||
},
|
||||
{
|
||||
"brand": "Google Chrome",
|
||||
"version": "93.0.1.2"
|
||||
},
|
||||
{
|
||||
"brand": " Not;A Brand",
|
||||
"version": "99.0.1.2"
|
||||
}
|
||||
],
|
||||
"mobile": true,
|
||||
"model": "Pixel 99",
|
||||
"platform": "Windows",
|
||||
"platformVersion": "13"
|
||||
};
|
||||
|
||||
it('Can read from client-hints headers using `withClientHints()`', function () {
|
||||
|
||||
assert.deepEqual(uap.ua_ch, ua_ch);
|
||||
assert.strictEqual(uap.ua, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36");
|
||||
assert.strictEqual(uap.browser.name, "Chrome");
|
||||
assert.strictEqual(uap.browser.version, "93.0.1.2");
|
||||
assert.strictEqual(uap.browser.major, "93");
|
||||
assert.strictEqual(browser.name, "Chrome");
|
||||
assert.strictEqual(browser.version, "93.0.1.2");
|
||||
assert.strictEqual(browser.major, "93");
|
||||
assert.strictEqual(uap.cpu.architecture, "arm64");
|
||||
assert.strictEqual(cpu.architecture, "arm64");
|
||||
assert.strictEqual(uap.device.type, "mobile");
|
||||
assert.strictEqual(uap.device.model, "Pixel 99");
|
||||
assert.strictEqual(uap.device.vendor, undefined);
|
||||
assert.strictEqual(device.type, "mobile");
|
||||
assert.strictEqual(device.model, "Pixel 99");
|
||||
assert.strictEqual(device.vendor, undefined);
|
||||
assert.strictEqual(uap.engine.name, 'Blink');
|
||||
assert.strictEqual(uap.engine.version, '110.0.0.0');
|
||||
assert.strictEqual(engine.name, 'Blink');
|
||||
assert.strictEqual(engine.version, '110.0.0.0');
|
||||
assert.strictEqual(uap.os.name, "Windows");
|
||||
assert.strictEqual(uap.os.version, "11");
|
||||
assert.strictEqual(os.name, "Windows");
|
||||
assert.strictEqual(os.version, "11");
|
||||
});
|
||||
|
||||
it('Only read from user-agent header when called without `withClientHints()`', function () {
|
||||
|
||||
uap = UAParser(headers);
|
||||
browser = new UAParser(headers).getBrowser();
|
||||
cpu = new UAParser(headers).getCPU();
|
||||
device = new UAParser(headers).getDevice();
|
||||
engine = new UAParser(headers).getEngine();
|
||||
os = new UAParser(headers).getOS();
|
||||
|
||||
assert.deepEqual(uap.ua_ch, ua_ch);
|
||||
assert.strictEqual(uap.browser.name, "Chrome");
|
||||
assert.strictEqual(uap.browser.version, "110.0.0.0");
|
||||
assert.strictEqual(uap.browser.major, "110");
|
||||
assert.strictEqual(uap.cpu.architecture, "amd64");
|
||||
assert.strictEqual(uap.device.type, undefined);
|
||||
assert.strictEqual(uap.device.model, undefined);
|
||||
assert.strictEqual(uap.device.vendor, undefined);
|
||||
assert.strictEqual(uap.engine.name, 'Blink');
|
||||
assert.strictEqual(uap.engine.version, '110.0.0.0');
|
||||
assert.strictEqual(uap.os.name, "Linux");
|
||||
assert.strictEqual(uap.os.version, "x86_64");
|
||||
});
|
||||
|
||||
it('Fallback to user-agent header when using `withClientHints()` but found no client hints-related headers', function () {
|
||||
|
||||
const headers2 = {
|
||||
'sec-ch-ua-mobile' : '?1',
|
||||
'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
|
||||
};
|
||||
|
||||
let uap = UAParser(headers);
|
||||
let browser = uap.browser;
|
||||
let cpu = uap.cpu;
|
||||
let device = uap.device;
|
||||
let engine = uap.engine;
|
||||
let os = uap.os;
|
||||
uap = UAParser(headers2).withClientHints();
|
||||
|
||||
it('Can read from client-hints headers', function () {
|
||||
ua_ch = {
|
||||
"architecture": undefined,
|
||||
"bitness": undefined,
|
||||
"brands": undefined,
|
||||
"fullVersionList": undefined,
|
||||
"mobile": true,
|
||||
"model": undefined,
|
||||
"platform": undefined,
|
||||
"platformVersion": undefined
|
||||
};
|
||||
|
||||
assert.strictEqual(browser.name, "Chrome");
|
||||
assert.strictEqual(browser.version, "93.0.1.2");
|
||||
assert.strictEqual(browser.major, "93");
|
||||
assert.strictEqual(cpu.architecture, "arm64");
|
||||
assert.strictEqual(device.type, "mobile");
|
||||
assert.strictEqual(device.model, "Pixel 99");
|
||||
assert.strictEqual(device.vendor, undefined);
|
||||
assert.strictEqual(engine.name, 'Blink');
|
||||
assert.strictEqual(engine.version, '110.0.0.0');
|
||||
assert.strictEqual(os.name, "Windows");
|
||||
assert.strictEqual(os.version, "11");
|
||||
});
|
||||
|
||||
it('Can read from user-agent header', function () {
|
||||
|
||||
uap = UAParser(headers2);
|
||||
browser = uap.browser;
|
||||
cpu = uap.cpu;
|
||||
device = uap.device;
|
||||
engine = uap.engine;
|
||||
os = uap.os;
|
||||
|
||||
assert.strictEqual(browser.name, "Chrome");
|
||||
assert.strictEqual(browser.version, "110.0.0.0");
|
||||
assert.strictEqual(browser.major, "110");
|
||||
assert.strictEqual(cpu.architecture, "amd64");
|
||||
assert.strictEqual(device.type, "mobile");
|
||||
assert.strictEqual(device.model, undefined);
|
||||
assert.strictEqual(device.vendor, undefined);
|
||||
assert.strictEqual(engine.name, 'Blink');
|
||||
assert.strictEqual(engine.version, '110.0.0.0');
|
||||
assert.strictEqual(os.name, "Linux");
|
||||
assert.strictEqual(os.version, "x86_64");
|
||||
assert.deepEqual(uap.ua_ch, ua_ch);
|
||||
assert.strictEqual(uap.browser.name, "Chrome");
|
||||
assert.strictEqual(uap.browser.version, "110.0.0.0");
|
||||
assert.strictEqual(uap.browser.major, "110");
|
||||
assert.strictEqual(uap.cpu.architecture, "amd64");
|
||||
assert.strictEqual(uap.device.type, "mobile");
|
||||
assert.strictEqual(uap.device.model, undefined);
|
||||
assert.strictEqual(uap.device.vendor, undefined);
|
||||
assert.strictEqual(uap.engine.name, 'Blink');
|
||||
assert.strictEqual(uap.engine.version, '110.0.0.0');
|
||||
assert.strictEqual(uap.os.name, "Linux");
|
||||
assert.strictEqual(uap.os.version, "x86_64");
|
||||
});
|
||||
});
|
||||
|
||||
@ -451,6 +531,12 @@ describe('Map UA-CH JS', () => {
|
||||
assert.strictEqual(result.cpu.architecture, 'amd64');
|
||||
assert.strictEqual(result.os.name, 'Android');
|
||||
|
||||
await uap.getDevice().withClientHints().then((device) => {
|
||||
assert.strictEqual(device.type, 'mobile');
|
||||
assert.strictEqual(device.vendor, undefined);
|
||||
assert.strictEqual(device.model, 'Galaxy S3');
|
||||
});
|
||||
|
||||
let result_without_ch = uap.getResult();
|
||||
|
||||
assert.strictEqual(result_without_ch.browser.name, undefined);
|
||||
|
Loading…
x
Reference in New Issue
Block a user