[helpers] split helpers into 2 new packages: user-agent-helpers & client-hints-helpers

This commit is contained in:
Faisal Salman 2023-08-23 14:38:34 +07:00
parent 129657673b
commit 3f105fe93b
16 changed files with 543 additions and 359 deletions

24
package-lock.json generated
View File

@ -23,7 +23,8 @@
], ],
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"src/helpers" "src/client-hints-helpers",
"src/user-agent-helpers"
], ],
"devDependencies": { "devDependencies": {
"@babel/parser": "7.15.8", "@babel/parser": "7.15.8",
@ -763,8 +764,12 @@
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==",
"dev": true "dev": true
}, },
"node_modules/@ua-parser-js/helpers": { "node_modules/@ua-parser-js/client-hints-helpers": {
"resolved": "src/helpers", "resolved": "src/client-hints-helpers",
"link": true
},
"node_modules/@ua-parser-js/user-agent-helpers": {
"resolved": "src/user-agent-helpers",
"link": true "link": true
}, },
"node_modules/@ungap/promise-all-settled": { "node_modules/@ungap/promise-all-settled": {
@ -3778,9 +3783,20 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"src/client-hints-helpers": {
"name": "@ua-parser-js/client-hints-helpers",
"version": "0.0.1",
"license": "MIT"
},
"src/helpers": { "src/helpers": {
"name": "@ua-parser-js/helpers", "name": "@ua-parser-js/helpers",
"version": "2.0.0-alpha.3", "version": "0.0.3",
"extraneous": true,
"license": "MIT"
},
"src/user-agent-helpers": {
"name": "@ua-parser-js/user-agent-helpers",
"version": "0.0.1",
"license": "MIT" "license": "MIT"
} }
} }

View File

@ -1,57 +1,64 @@
#!/usr/bin/env node #!/usr/bin/env node
/* jshint esversion: 6 */ /* jshint esversion: 6 */
const fs = require('fs'); const fs = require('fs');
const PATH = {
main : { const generateMJS = (module) => {
src : 'src/main/ua-parser.js', let { src, dest, title, replacements } = module;
dest : 'src/main/ua-parser.mjs',
title : ''
},
enums : {
src : 'src/enums/ua-parser-enums.js',
dest :'src/enums/ua-parser-enums.mjs',
title : 'enums'
},
extensions : {
src : 'src/extensions/ua-parser-extensions.js',
dest : 'src/extensions/ua-parser-extensions.mjs',
title : 'extensions'
},
helpers : {
src : 'src/helpers/ua-parser-helpers.js',
dest : 'src/helpers/ua-parser-helpers.mjs',
title : 'helpers'
}
};
const generateMJS = (module, replacers) => {
const { src, dest, title } = PATH[module];
let text = fs.readFileSync(src, 'utf-8'); let text = fs.readFileSync(src, 'utf-8');
replacers.forEach(replacer => {
text = text.replace(replacer[0], replacer[1]); replacements.push(
[/const (.+?)\s*=\s*require\((.+)\)/ig, 'import $1 from $2'],
[/module\.exports =/ig, 'export']
);
replacements.forEach(rep => {
text = text.replace(rep[0], rep[1]);
}); });
console.log(`Generate ${dest}`); console.log(`Generate ${dest}`);
fs.writeFileSync(dest, fs.writeFileSync(dest,
`// Generated ESM version of UAParser.js ${title} `// Generated ESM version of ${title}
// DO NOT EDIT THIS FILE! // DO NOT EDIT THIS FILE!
// Source: /${src} // Source: /${src}
${text}`, 'utf-8'); ${text}`, 'utf-8');
}; };
// ua-parser.mjs const modules = [
generateMJS('main', [ {
[/\(func[\s\S]+strict\';/ig, ''], src : 'src/main/ua-parser.js',
[/esversion\: 3/ig, 'esversion: 6'], dest : 'src/main/ua-parser.mjs',
[/\/[\/\s]+export[\s\S]+/ig,'export {UAParser};'] title : 'ua-parser-js',
]); replacements : [
[/\(func[\s\S]+strict\';/ig, ''],
[/esversion\: 3/ig, 'esversion: 6'],
[/\/[\/\s]+export[\s\S]+/ig,'export {UAParser};']
]
},{
src : 'src/enums/ua-parser-enums.js',
dest :'src/enums/ua-parser-enums.mjs',
title : 'ua-parser-js/enums',
replacements : []
},
{
src : 'src/extensions/ua-parser-extensions.js',
dest : 'src/extensions/ua-parser-extensions.mjs',
title : 'ua-parser-js/extensions',
replacements : []
},
{
src : 'src/user-agent-helpers/user-agent-helpers.js',
dest : 'src/user-agent-helpers/user-agent-helpers.mjs',
title : '@ua-parser-js/user-agent-helpers',
replacements : []
},
{
src : 'src/client-hints-helpers/client-hints-helpers.js',
dest : 'src/client-hints-helpers/client-hints-helpers.mjs',
title : '@ua-parser-js/client-hints-helpers',
replacements : []
}
];
// ua-parser-enum.mjs modules.forEach(module => generateMJS(module));
generateMJS('enums', [[/module\.exports =/ig, 'export']]);
// ua-parser-extension.mjs
generateMJS('extensions', [[/module\.exports =/ig, 'export']]);
// ua-parser-helpers.mjs
generateMJS('helpers', [[/module\.exports =/ig, 'export']]);

View File

@ -32,6 +32,4 @@ export interface ClientHintsHTTPHeaders {
'sec-ch-ua-wow64'?: string; 'sec-ch-ua-wow64'?: string;
} }
export function isFrozenUA(ua: string): boolean;
export function unfreezeUA(): Promise<string>;
export function UACHParser(headers: ClientHintsHTTPHeaders): ClientHintsJSHighEntropy; export function UACHParser(headers: ClientHintsHTTPHeaders): ClientHintsJSHighEntropy;

View File

@ -0,0 +1,88 @@
////////////////////////////////////////////////////
/* A collection of utility methods for client-hints
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
MIT License */
///////////////////////////////////////////////////
/*jshint esversion: 11 */
const UACHMap = {
'sec-ch-ua-arch' : {
prop : 'architecture',
type : 'sf-string'
},
'sec-ch-ua-bitness' : {
prop : 'bitness',
type : 'sf-string'
},
'sec-ch-ua' : {
prop : 'brands',
type : 'sf-list'
},
'sec-ch-ua-form-factor' : {
prop : 'formFactor',
type : 'sf-string'
},
'sec-ch-ua-full-version-list' : {
prop : 'fullVersionList',
type : 'sf-list'
},
'sec-ch-ua-mobile' : {
prop : 'mobile',
type : 'sf-boolean',
},
'sec-ch-ua-model' : {
prop : 'model',
type : 'sf-string',
},
'sec-ch-ua-platform' : {
prop : 'platform',
type : 'sf-string'
},
'sec-ch-ua-platform-version' : {
prop : 'platformVersion',
type : 'sf-string'
},
'sec-ch-ua-wow64' : {
prop : 'wow64',
type : 'sf-boolean'
}
};
const UACHParser = (headers) => {
const parse = (str, type) => {
if (!str) {
return '';
}
switch (type) {
case 'sf-boolean':
return /\?1/.test(str);
case 'sf-list':
return str.replace(/\\?\"/g, '')
.split(', ')
.map(brands => {
const [brand, version] = brands.split(';v=');
return {
brand : brand,
version : version
};
});
case 'sf-string':
default:
return str.replace(/\\?\"/g, '');
}
};
let ch = {};
Object.keys(UACHMap).forEach(field => {
if (headers.hasOwnProperty(field)) {
const { prop, type } = UACHMap[field];
ch[prop] = parse(headers[field], type);
}
});
return ch;
};
module.exports = {
UACHParser
};

View File

@ -0,0 +1,92 @@
// Generated ESM version of @ua-parser-js/client-hints-helpers
// DO NOT EDIT THIS FILE!
// Source: /src/client-hints-helpers/client-hints-helpers.js
////////////////////////////////////////////////////
/* A collection of utility methods for client-hints
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
MIT License */
///////////////////////////////////////////////////
/*jshint esversion: 11 */
const UACHMap = {
'sec-ch-ua-arch' : {
prop : 'architecture',
type : 'sf-string'
},
'sec-ch-ua-bitness' : {
prop : 'bitness',
type : 'sf-string'
},
'sec-ch-ua' : {
prop : 'brands',
type : 'sf-list'
},
'sec-ch-ua-form-factor' : {
prop : 'formFactor',
type : 'sf-string'
},
'sec-ch-ua-full-version-list' : {
prop : 'fullVersionList',
type : 'sf-list'
},
'sec-ch-ua-mobile' : {
prop : 'mobile',
type : 'sf-boolean',
},
'sec-ch-ua-model' : {
prop : 'model',
type : 'sf-string',
},
'sec-ch-ua-platform' : {
prop : 'platform',
type : 'sf-string'
},
'sec-ch-ua-platform-version' : {
prop : 'platformVersion',
type : 'sf-string'
},
'sec-ch-ua-wow64' : {
prop : 'wow64',
type : 'sf-boolean'
}
};
const UACHParser = (headers) => {
const parse = (str, type) => {
if (!str) {
return '';
}
switch (type) {
case 'sf-boolean':
return /\?1/.test(str);
case 'sf-list':
return str.replace(/\\?\"/g, '')
.split(', ')
.map(brands => {
const [brand, version] = brands.split(';v=');
return {
brand : brand,
version : version
};
});
case 'sf-string':
default:
return str.replace(/\\?\"/g, '');
}
};
let ch = {};
Object.keys(UACHMap).forEach(field => {
if (headers.hasOwnProperty(field)) {
const { prop, type } = UACHMap[field];
ch[prop] = parse(headers[field], type);
}
});
return ch;
};
export {
UACHParser
};

View File

@ -1,13 +1,13 @@
{ {
"title": "UAParser.js Helpers", "title": "Client-Hints Helpers",
"name": "@ua-parser-js/helpers", "name": "@ua-parser-js/client-hints-helpers",
"version": "0.0.3", "version": "0.0.1",
"author": "Faisal Salman <f@faisalman.com>", "author": "Faisal Salman <f@faisalman.com>",
"description": "A collection of utility methods for UAParser.js", "description": "A collection of utility methods for working with client-hints",
"main": "ua-parser-helpers.js", "main": "client-hints-helpers.js",
"module": "ua-parser-helpers.mjs", "module": "client-hints-helpers.mjs",
"scripts": { "scripts": {
"test": "echo 1" "test": "mocha ../../test/mocha-test-helpers"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -0,0 +1,75 @@
# @ua-parser-js/client-hints-helpers
This is a [UAParser.js](https://github.com/faisalman/ua-parser-js) module that contains a collection of utility methods for working with user-agent client-hints.
```sh
npm i @ua-parser-js/client-hints-helpers
```
### * `UACHParser(headers:object):object`
Parse user-agent client-hints HTTP headers (sec-ch-ua) into its JS API equivalent
## Code Example
```js
import { UACHParser } from '@ua-parser-js/client-hints-helpers';
/*
Suppose we're in a server having this client hints data:
const headers = {
'sec-ch-ua' : '"Chromium";v="93", "Google Chrome";v="93", " Not;A Brand";v="99"',
'sec-ch-ua-full-version-list' : '"Chromium";v="93.0.1.2", "Google Chrome";v="93.0.1.2", " Not;A Brand";v="99.0.1.2"',
'sec-ch-ua-arch' : 'arm',
'sec-ch-ua-bitness' : '64',
'sec-ch-ua-mobile' : '?1',
'sec-ch-ua-model' : 'Pixel 99',
'sec-ch-ua-platform' : 'Linux',
'sec-ch-ua-platform-version' : '13',
'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
};
*/
const userAgentData = UACHParser(headers);
console.log(userAgentData);
/*
{
"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": "Linux",
"platformVersion": "13"
}
*/
```

View File

@ -1,131 +0,0 @@
# @ua-parser-js/helpers
This package contains a collection of utility methods for [UAParser.js](https://github.com/faisalman/ua-parser-js)
```sh
npm i @ua-parser-js/helpers
```
### * `isFrozenUA(ua:string):boolean`
Check whether a user-agent string match with [frozen user-agent pattern](https://www.chromium.org/updates/ua-reduction/)
### * `unfreezeUA():Promise<string>`
Construct new unfreezed user-agent string using real data from client hints
### * `UACHParser(headers: object): object`
Parse client hints HTTP headers (sec-ch-ua) into its JS API equivalent
## Code Example
```js
import { isFrozenUA } from '@ua-parser-js/helpers';
const regularMobileUA = "Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.1234.56 Mobile Safari/537.36";
const freezedMobileUA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36";
console.log(isFrozenUA(regularMobileUA));
// false
console.log(isFrozenUA(freezedMobileUA));
// true
```
```js
import { unfreezeUA } from '@ua-parser-js/helpers';
/*
Suppose we're in a browser having this client hints data:
{
fullVersionList: [
{
brand: 'New Browser',
version: '110.1.2.3'
},
{
brand: 'Chromium',
version: '110.1.2.3'
},
{
brand: 'Not(A:Brand',
version: '110'
}
],
platform: 'Windows',
platformVersion: '13.0.0'
}
And a freezed user-agent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36'
*/
unfreezeUA()
.then(ua => console.log(ua));
// 'Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) New Browser/110.1.2.3 Chromium/110.1.2.3 Safari/537.36'
```
```js
import { UACHParser } from '@ua-parser-js/helpers';
/*
Suppose we're in a server having this client hints data:
const headers = {
'sec-ch-ua' : '"Chromium";v="93", "Google Chrome";v="93", " Not;A Brand";v="99"',
'sec-ch-ua-full-version-list' : '"Chromium";v="93.0.1.2", "Google Chrome";v="93.0.1.2", " Not;A Brand";v="99.0.1.2"',
'sec-ch-ua-arch' : 'arm',
'sec-ch-ua-bitness' : '64',
'sec-ch-ua-mobile' : '?1',
'sec-ch-ua-model' : 'Pixel 99',
'sec-ch-ua-platform' : 'Linux',
'sec-ch-ua-platform-version' : '13',
'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
};
*/
const userAgentData = UACHParser(headers);
console.log(userAgentData);
/*
{
"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": "Linux",
"platformVersion": "13"
}
*/
```

View File

@ -1,44 +0,0 @@
// Generated ESM version of UAParser.js helpers
// DO NOT EDIT THIS FILE!
// Source: /src/helpers/ua-parser-helpers.js
///////////////////////////////////////////////
/* Helpers for UAParser.js
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
MIT License */
//////////////////////////////////////////////
/*jshint esversion: 6 */
/*
# Reference:
https://www.chromium.org/updates/ua-reduction/
# Desktop
---
Format:
Mozilla/5.0 (<unifiedPlatform>) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<majorVersion>.0.0.0 Safari/537.36
Possible <unifiedPlatform> values:
- Windows NT 10.0; Win64; x64
- Macintosh; Intel Mac OS X 10_15_7
- X11; Linux x86_64
- X11; CrOS x86_64 14541.0.0
- Fuchsia
# Mobile & Tablet: (except iOS/Android WebView)
---
Format:
Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<majorVersion>.0.0.0 <deviceCompat> Safari/537.36
Possible <deviceCompat> values:
- "Mobile"
- "" (empty string for Tablets & Desktop)
*/
const isFrozenUA = ua => /Mozilla\/5\.0 \((Windows NT 10\.0; Win64; x64|Macintosh; Intel Mac OS X 10_15_7|X11; Linux x86_64|X11; CrOS x86_64 14541\.0\.0|Fuchsia|Linux; Android 10; K)\) AppleWebKit\/537\.36 \(KHTML, like Gecko\) Chrome\/\d+\.0\.0\.0 (Mobile )?Safari\/537\.36/.test(ua);
export {
isFrozenUA
};

View File

@ -0,0 +1,32 @@
{
"title": "User-Agent Helpers",
"name": "@ua-parser-js/user-agent-helpers",
"version": "0.0.1",
"author": "Faisal Salman <f@faisalman.com>",
"description": "A collection of utility methods for working with user-agent",
"main": "user-agent-helpers.js",
"module": "user-agent-helpers.mjs",
"scripts": {
"test": "mocha ../../test/mocha-test-helpers"
},
"repository": {
"type": "git",
"url": "git+https://github.com/faisalman/ua-parser-js.git"
},
"keywords": [
"ua-parser-js",
"browser-detection",
"device-detection",
"os-detection",
"user-agent",
"client-hints"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/faisalman/ua-parser-js/issues"
},
"homepage": "https://github.com/faisalman/ua-parser-js#readme",
"dependencies": {
"@ua-parser-js/client-hints-helpers": "*"
}
}

View File

@ -0,0 +1,67 @@
# @ua-parser-js/user-agent-helpers
This is a [UAParser.js](https://github.com/faisalman/ua-parser-js) module that contains a collection of utility methods for working with user-agent.
```sh
npm i @ua-parser-js/user-agent-helpers
```
### * `isFrozenUA(ua:string):boolean`
Check whether a user-agent string match with [frozen user-agent pattern](https://www.chromium.org/updates/ua-reduction/)
### * `unfreezeUA([ua:string,ch:object]|[headers:object]):Promise<string>`
Construct new unfreezed user-agent string using real data from client hints
## Code Example
```js
import { isFrozenUA } from '@ua-parser-js/user-agent-helpers';
const regularMobileUA = "Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.1234.56 Mobile Safari/537.36";
const freezedMobileUA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36";
console.log(isFrozenUA(regularMobileUA));
// false
console.log(isFrozenUA(freezedMobileUA));
// true
```
```js
import { unfreezeUA } from '@ua-parser-js/user-agent-helpers';
/*
Suppose we're in a browser having this client hints data:
{
fullVersionList: [
{
brand: 'New Browser',
version: '110.1.2.3'
},
{
brand: 'Chromium',
version: '110.1.2.3'
},
{
brand: 'Not(A:Brand',
version: '110'
}
],
platform: 'Windows',
platformVersion: '13.0.0',
architecture: 'arm'
}
With a frozen user-agent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36'
*/
// Now let's generate a complete user-agent:
unfreezeUA()
.then(newUA => console.log(newUA));
// 'Mozilla/5.0 (Windows NT 11.0; ARM) AppleWebKit/537.36 (KHTML, like Gecko) New Browser/110.1.2.3 Chromium/110.1.2.3 Safari/537.36'
```

View File

@ -0,0 +1,4 @@
export function isFrozenUA(ua: string): boolean;
export function unfreezeUA(): Promise<string>;
export function unfreezeUA(ua: string, ch: ClientHintsJSHighEntropy): Promise<string>;
export function unfreezeUA(headers: ClientHintsHTTPHeaders): Promise<string>;

View File

@ -1,12 +1,14 @@
/////////////////////////////////////////////// ////////////////////////////////////////////////////
/* A collection of utility methods for UAParser.js /* A collection of utility methods for user-agent
https://github.com/faisalman/ua-parser-js https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com> Author: Faisal Salman <f@faisalman.com>
MIT License */ MIT License */
////////////////////////////////////////////// ///////////////////////////////////////////////////
/*jshint esversion: 11 */ /*jshint esversion: 11 */
const { UACHParser } = require('@ua-parser-js/client-hints-helpers');
/* /*
# Reference: # Reference:
https://www.chromium.org/updates/ua-reduction/ https://www.chromium.org/updates/ua-reduction/
@ -89,84 +91,7 @@ const unfreezeUA = async (ua, ch) => {
return ua; return ua;
}; };
const UACHMap = {
'sec-ch-ua-arch' : {
prop : 'architecture',
type : 'sf-string'
},
'sec-ch-ua-bitness' : {
prop : 'bitness',
type : 'sf-string'
},
'sec-ch-ua' : {
prop : 'brands',
type : 'sf-list'
},
'sec-ch-ua-form-factor' : {
prop : 'formFactor',
type : 'sf-string'
},
'sec-ch-ua-full-version-list' : {
prop : 'fullVersionList',
type : 'sf-list'
},
'sec-ch-ua-mobile' : {
prop : 'mobile',
type : 'sf-boolean',
},
'sec-ch-ua-model' : {
prop : 'model',
type : 'sf-string',
},
'sec-ch-ua-platform' : {
prop : 'platform',
type : 'sf-string'
},
'sec-ch-ua-platform-version' : {
prop : 'platformVersion',
type : 'sf-string'
},
'sec-ch-ua-wow64' : {
prop : 'wow64',
type : 'sf-boolean'
}
};
const UACHParser = (headers) => {
const parse = (str, type) => {
if (!str) {
return '';
}
switch (type) {
case 'sf-boolean':
return /\?1/.test(str);
case 'sf-list':
return str.replace(/\\?\"/g, '')
.split(', ')
.map(brands => {
const [brand, version] = brands.split(';v=');
return {
brand : brand,
version : version
};
});
case 'sf-string':
default:
return str.replace(/\\?\"/g, '');
}
};
let ch = {};
Object.keys(UACHMap).forEach(field => {
if (headers.hasOwnProperty(field)) {
const { prop, type } = UACHMap[field];
ch[prop] = parse(headers[field], type);
}
});
return ch;
};
module.exports = { module.exports = {
isFrozenUA, isFrozenUA,
unfreezeUA, unfreezeUA
UACHParser
}; };

View File

@ -0,0 +1,101 @@
// Generated ESM version of @ua-parser-js/user-agent-helpers
// DO NOT EDIT THIS FILE!
// Source: /src/user-agent-helpers/user-agent-helpers.js
////////////////////////////////////////////////////
/* A collection of utility methods for user-agent
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
MIT License */
///////////////////////////////////////////////////
/*jshint esversion: 11 */
import { UACHParser } from '@ua-parser-js/client-hints-helpers';
/*
# Reference:
https://www.chromium.org/updates/ua-reduction/
# Desktop
---
Format:
Mozilla/5.0 (<unifiedPlatform>) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<majorVersion>.0.0.0 Safari/537.36
Possible <unifiedPlatform> values:
- Windows NT 10.0; Win64; x64
- Macintosh; Intel Mac OS X 10_15_7
- X11; Linux x86_64
- X11; CrOS x86_64 14541.0.0
- Fuchsia
# Mobile & Tablet: (except iOS/Android WebView)
---
Format:
Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<majorVersion>.0.0.0 <deviceCompat> Safari/537.36
Possible <deviceCompat> values:
- "Mobile"
- "" (empty string for Tablets & Desktop)
*/
const isFrozenUA = ua => /^Mozilla\/5\.0 \((Windows NT 10\.0; Win64; x64|Macintosh; Intel Mac OS X 10_15_7|X11; Linux x86_64|X11; CrOS x86_64 14541\.0\.0|Fuchsia|Linux; Android 10; K)\) AppleWebKit\/537\.36 \(KHTML, like Gecko\) Chrome\/\d+\.0\.0\.0 (Mobile )?Safari\/537\.36$/.test(ua);
const unfreezeUA = async (ua, ch) => {
const env = typeof navigator == 'undefined' ? 'node' : 'browser';
if (env == 'node') {
if (!ua['user-agent']) {
throw new Error('User-Agent header not found');
}
ch = UACHParser(ua);
ua = ua['user-agent'];
} else {
ua = ua || navigator.userAgent;
ch = ch || await navigator.userAgentData?.getHighEntropyValues(['arch', 'bitness', 'fullVersionList', 'model', 'platform', 'platformVersion', 'wow64']);
}
if (isFrozenUA(ua) && ch) {
switch (ch.platform) {
case 'Windows':
let [major, minor] = ch.platformVersion
.split('.')
.map(num => parseInt(num, 10));
major = (major < 1) ? '6' : (major >= 13) ? '11' : '10';
ua = ua .replace(/(?<OS>Windows NT) 10\.0/, `$<OS> ${major}.${minor}`)
.replace(/; (?<ARCH>Win64; x64)/,
(ch.architecture == 'arm') ?
'; ARM' :
(ch.wow64) ?
'; WOW64' :
(ch.architecture == 'x86' && ch.bitness != '64') ?
'' : '; $<ARCH>');
break;
case 'Android':
ua = ua.replace(/(?<OS>Android) 10; K/, `$<OS> ${ch.platformVersion}; ${ch.model}`);
break;
case 'Linux':
case 'Chrome OS':
ua = ua.replace(/(?<ARCH>x86_64)/,
(ch.architecture == 'arm') ?
((ch.bitness == '64') ? 'arm64' : 'arm') :
(ch.architecture == 'x86' && ch.bitness != '64') ?
'x86' : '$<ARCH>');
break;
case 'macOS':
ua = ua.replace(/(?<OS>Mac OS X) 10_15_7/, `$<OS> ${ch.platformVersion.replace(/\./, '_')}`);
break;
}
if (ch.fullVersionList) {
ua = ua.replace(/Chrome\/\d+\.0\.0\.0 /,
ch.fullVersionList
.filter(browser => !/not.a.brand/i.test(browser.brand))
.map(browser => `${browser.brand.replace(/^google /i,'')}/${browser.version} `)
.join(''));
}
}
return ua;
};
export {
isFrozenUA,
unfreezeUA
};

View File

@ -1,4 +1,5 @@
const { isFrozenUA, unfreezeUA, UACHParser } = require('@ua-parser-js/helpers'); const { isFrozenUA, unfreezeUA } = require('@ua-parser-js/user-agent-helpers');
const { UACHParser } = require('@ua-parser-js/client-hints-helpers');
const assert = require('assert'); const assert = require('assert');
describe('isFrozenUA()', () => { describe('isFrozenUA()', () => {
@ -54,7 +55,7 @@ describe('unfreezeUA()', () => {
}); });
describe('UACHParser()', () => { describe('UACHParser()', () => {
it('parse client hints HTTP headers (sec-ch-ua) into a JavaScript object', () => { it('parse client hints HTTP headers (sec-ch-ua) into a client hints-like JavaScript object', () => {
assert.deepEqual(UACHParser(headers), { assert.deepEqual(UACHParser(headers), {
"architecture": "arm", "architecture": "arm",
"bitness": "64", "bitness": "64",

View File

@ -1,47 +0,0 @@
// @ts-check
import { test, expect } from '@playwright/test';
import { unfreezeUA } from '@ua-parser-js/helpers';
test('test for unfreezeUA() method', async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(navigator, 'userAgentData', {
value: {
brands: [],
platform: 'Windows',
mobile: false,
getHighEntropyValues: () => {
return Promise.resolve({
architecture: 'x86',
bitness: '64',
fullVersionList: [
{
brand: 'New Browser',
version: '110.1.2.3'
},
{
brand: 'Chromium',
version: '110.1.2.3'
},
{
brand: 'Not(A:Brand',
version: '110'
}
],
platform: 'Windows',
platformVersion: '0.3'
});
}
}
});
Object.defineProperty(navigator, 'userAgent', {
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Safari/537.36'
});
});
await page.goto('about:blank');
await page.addScriptTag({ path: './src/helpers/ua-parser-helpers.js' });
// @ts-ignore
const ua = await page.evaluate(async () => await unfreezeUA());
expect(ua).toBe('Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) New Browser/110.1.2.3 Chromium/110.1.2.3 Safari/537.36');
});