From c78346d3b4b6ead2a558d835f439eda6115d7747 Mon Sep 17 00:00:00 2001 From: Faisal Salman Date: Wed, 15 Mar 2023 23:22:34 +0700 Subject: [PATCH] Returns `withClientHints()` as `Thenable` in nodejs / non-client-hints browsers --- readme.md | 25 +++++++++++-------- src/ua-parser.js | 65 ++++++++++++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/readme.md b/readme.md index c376735..93f5c81 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,11 @@ JavaScript library to detect Browser, Engine, OS, CPU, and Device type/model fro

UAParser.js has been upgraded to detect comprehensive device data based on the User-Agent and User-Agent Client Hints.

This package supports all device types including Apple and Android devices and can be used either in a browser (client-side) or Node.js environment (server-side).

Visit ↗ 51Degrees UAParser to get started.

+ + +↗ Become a sponsor + + @@ -41,13 +46,11 @@ JavaScript library to detect Browser, Engine, OS, CPU, and Device type/model fro # Documentation ### UAParser([user-agent:string][,extensions:object][,headers:object(since@1.1)]) -In The Browser environment you dont need to pass the user-agent string to the function, you can just call the funtion and it should automatically get the string from the `window.navigator.userAgent`, but that is not the case in nodejs. The user-agent string must be passed in nodejs for the function to work. -Usually you can find the user agent in: -`request.headers["user-agent"]`. +In the browser environment you dont need to pass the user-agent string to the function, you can just call the funtion and it should automatically get the string from the `window.navigator.userAgent`, but that is not the case in nodejs. The user-agent string must be passed in' nodejs for the function to work. Usually you can find the user agent in: `request.headers["user-agent"]`. ## 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. +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][,headers:object(since@1.1)])` ```js @@ -183,7 +186,7 @@ Ubuntu, Unix, VectorLinux, Viera, watchOS, WebOS, Windows [Phone/Mobile], Zenwal * set UA string to be parsed * returns current instance -#### * `is()` utility `since@1.1` +#### * `is():boolean` utility `since@1.1` ```js // Is just a shorthand to check whether specified item has a property with equals value (case-insensitive) @@ -247,7 +250,7 @@ let engine = uap.getEngine(); engine.is("Blink"); // true ``` -#### * `toString()` utility `since@1.1` +#### * `toString():string` utility `since@1.1` ```js // Retrieve full-name values as a string @@ -288,9 +291,9 @@ engine.version; // "28.0.1500.95" engine.toString(); // "Blink 28.0.1500.95" ``` -#### * `withClientHints()` `since@1.1` +#### * `withClientHints():Promise|Thenable` `since@1.1` -Unlike reading user-agent data, accessing client-hints data in browser-environment must be done in an asynchronous way. Worry not, you can chain the UAParser's `get*` method with `withClientHints()` to read the client-hints data as well that will return the updated data as a `Promise`. +Unlike reading user-agent data, accessing client-hints data in browser-environment must be done in an asynchronous way. Worry not, you can chain the UAParser's `get*` method with `withClientHints()` to read the client-hints data as well that will return the updated data as a `Promise`. In nodejs-environment / browser-environment with non-secure context or without client-hints support (basically anything that's not chromium-based) this will return the updated data as a `Thenable` (can be chained with `then()`). ```js (async function () { @@ -451,11 +454,13 @@ http.createServer(function (req, res) { var ua = uap(req.headers['user-agent']); /* // BEGIN since@1.1 - you can also pass client-hints data to UAParser - + + // note: only works in secure context (https:// or localhost or file://) + var getHighEntropyValues = 'Sec-CH-UA-Full-Version-List, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness'; res.setHeader('Accept-CH', getHighEntropyValues); res.setHeader('Critical-CH', getHighEntropyValues); - + var ua = uap(req.headers); // END since@1.1 */ diff --git a/src/ua-parser.js b/src/ua-parser.js index 73e26f8..c017e8c 100755 --- a/src/ua-parser.js +++ b/src/ua-parser.js @@ -909,7 +909,10 @@ return str ? str : UNDEF_TYPE; }; UAParserData.prototype.withClientHints = function () { + + // nodejs / non-client-hints browsers if (!NAVIGATOR_UADATA) { + var HTTP_UACH = uaCH; switch (itemType) { case UA_BROWSER: @@ -923,52 +926,54 @@ 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 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(); } } + + // browsers based on chromium 85+ return NAVIGATOR_UADATA .getHighEntropyValues(CH_ALL_VALUES) .then(function (res) { - var JS_UACH = new UAParserDataCH(res, false), - 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).parseCH().get(); - + var JS_UACH = new UAParserDataCH(res, false); switch (itemType) { case UA_BROWSER: - return browser; + return UAParserBrowser(ua, rgxMap, JS_UACH).parseCH().get(); case UA_CPU: - return cpu; + return new UAParserCPU(ua, rgxMap, JS_UACH).parseCH().get(); case UA_DEVICE: - return device; + return new UAParserDevice(ua, rgxMap, JS_UACH).parseCH().get(); case UA_ENGINE: - return engine; + return new UAParserEngine(ua, rgxMap).get(); case UA_OS: - return os; + return new UAParserOS(ua, rgxMap, JS_UACH).parseCH().get(); default : - return { - 'ua' : ua, - 'ua_ch' : JS_UACH, - 'browser' : browser, - 'cpu' : cpu, - 'device' : device, - 'engine' : engine, - 'os' : os - }; + 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 (!NAVIGATOR_UADATA) { + UAParserData.prototype.then = function (cb) { + cb(this); + return this; + }; + } return new UAParserData(); })(data); }