Bump version 2.0.7

This commit is contained in:
Faisal Salman
2025-12-09 13:15:29 +07:00
parent 82d50451a2
commit 7bc177de79
19 changed files with 365 additions and 282 deletions

View File

@@ -54,21 +54,52 @@
- `browser.name`, `browser.type`, `cpu.architecture`, `device.type`, `device.vendor`, `engine.name`, `os.name`
- **`'ua-parser-js/extensions'`**: Predefined extensions for various use cases:
- `Bots`, `Crawlers`, `CLIs`, `Emails`, `ExtraDevices`, `Fetchers`, `InApps`, `Libraries`, `Mediaplayers`
- `Bots`, `Crawlers`, `CLIs`, `Emails`, `ExtraDevices`, `Fetchers`, `InApps`, `Libraries`, `Mediaplayers`, `Vehicles`
- **`'ua-parser-js/helpers'`**: Provides utility methods to extend detection functionality:
- `getDeviceVendor()`: Guesses the device vendor based on its model name
- `isAppleSilicon()`: Detects Apple Silicon device properties
- `isAIBot()`: Checks if the user-agent is an AI bot
- `isFrozenUA()`: Checks if the user-agent matches a frozen/reduced user-agent pattern
- **`'ua-parser-js/bot-detection'`**:
- `isAIAssistant()`: Checks if the user-agent is an AI assistant
- `isAICrawler()`: Checks if the user-agent is an AI crawler
- `isBot()`: Checks if the user-agent is a bot
- **`'ua-parser-js/browser-detection'`**:
- `isChromeFamily()`: Checks if the browser is Chrome-based (uses Blink engine) — e.g., New Opera, New Edge, Vivaldi, Brave, Arc, etc.
- `isElectron()`: Detects if current window is running within Electron
- `isFromEU()`: Detects if current browser's timezone is from an EU country
- `isFrozenUA()`: Checks if the user-agent matches a frozen/reduced user-agent pattern
- `isStandalonePWA()`: Detects if current window is a standalone PWA
- **`'ua-parser-js/device-detection'`**:
- `getDeviceVendor()`: Guesses the device vendor based on its model name
- `isAppleSilicon()`: Detects Apple Silicon device properties
---
## Version 2.0.7
- Add support for chaining `withClientHints()` & `withFeatureCheck()`
- Add new browser: Atlas, Steam
- Add new device vendor: Anbernic, Logitech, Valve
- Improve device detection: Xiaomi
- Improve OS detection: iOS
- Split `helpers` submodule into several new submodules:
- `bot-detection`:
- `isAIAssistant()`
- `isAICrawler()`
- `isBot()`
- `browser-detection`
- `isChromeFamily()`
- `isElectron()`
- `isFromEU()`
- `isStandalonePWA()`
- `device-detection`
- `getDeviceVendor()`
- `isAppleSilicon()`
- Update `extensions` submodule:
- Add new fetcher: Nova Act
- Add new library: Bun, Dart, Deno, hackney, Node.js, rest-client, undici
## Version 2.0.6
- Add new CLI feature: processing batch user-agent data from file and output as JSON
- Fix `setUA()`: trim leading space from user-agent string input

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

32
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ua-parser-js",
"version": "2.0.6",
"version": "2.0.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ua-parser-js",
"version": "2.0.6",
"version": "2.0.7",
"funding": [
{
"type": "opencollective",
@@ -33,7 +33,7 @@
"devDependencies": {
"@babel/parser": "7.15.8",
"@babel/traverse": "7.23.2",
"@playwright/test": "^1.49.0",
"@playwright/test": "^1.57.0",
"jshint": "~2.13.6",
"mocha": "~8.2.0",
"requirejs": "2.3.2",
@@ -343,12 +343,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz",
"integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==",
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.49.0"
"playwright": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -2042,12 +2043,13 @@
}
},
"node_modules/playwright": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz",
"integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==",
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.49.0"
"playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -2060,10 +2062,11 @@
}
},
"node_modules/playwright-core": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz",
"integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==",
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
@@ -2077,6 +2080,7 @@
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"

View File

@@ -1,7 +1,7 @@
{
"title": "UAParser.js",
"name": "ua-parser-js",
"version": "2.0.6",
"version": "2.0.7",
"author": "Faisal Salman <f@faisalman.com> (http://faisalman.com)",
"description": "Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent & Client Hints data. Supports browser & node.js environment",
"keywords": [
@@ -246,7 +246,7 @@
"devDependencies": {
"@babel/parser": "7.15.8",
"@babel/traverse": "7.23.2",
"@playwright/test": "^1.49.0",
"@playwright/test": "^1.57.0",
"jshint": "~2.13.6",
"mocha": "~8.2.0",
"requirejs": "2.3.2",

View File

@@ -12,8 +12,8 @@
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { Extension, BrowserType } from '../enums/ua-parser-enums.mjs';
import { Bots, Crawlers, Fetchers } from '../extensions/ua-parser-extensions.mjs';
import { BrowserType, Extension } from '../enums/ua-parser-enums.mjs';
const { Crawler, Fetcher } = Extension.BrowserName;
class BotList {
@@ -40,6 +40,9 @@ const BotTypes = new BotList(Bots, 'type', [
const AIAssistants = new BotList(Fetchers, 'name', [
// Amazon
Fetcher.AMAZON_NOVA_ACT,
// Anthropic
Fetcher.ANTHROPIC_CLAUDE_USER,

View File

@@ -12,7 +12,9 @@
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { EngineName } from '../enums/ua-parser-enums.mjs';
import { isStandalonePWA } from 'is-standalone-pwa';
import { isFromEU } from 'detect-europe-js';
const isChromeFamily = val => !!(
(typeof val === 'string' ?
@@ -20,11 +22,13 @@ const isChromeFamily = val => !!(
val.engine
)?.is(EngineName.BLINK));
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
const isElectron = () => !!(
process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
export {
isChromeFamily,
isElectron,
isFromEU,
isStandalonePWA
}

View File

@@ -3,7 +3,7 @@
// Source: /src/enums/ua-parser-enums.js
///////////////////////////////////////////////
/* Enums for UAParser.js v2.0.6
/* Enums for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -19,6 +19,7 @@ export const BrowserName: Readonly<{
AMAYA: 'Amaya',
ANDROID: 'Android Browser',
ARORA: 'Arora',
ATLAS: 'Atlas',
AVANT: 'Avant',
AVAST: 'Avast Secure Browser',
AVG: 'AVG Secure Browser',
@@ -147,6 +148,7 @@ export const BrowserName: Readonly<{
SNAPCHAT: 'Snapchat',
SOGOU_EXPLORER: 'Sogou Explorer',
SOGOU_MOBILE: 'Sogou Mobile',
STEAM: 'Steam',
SURF: 'Surf',
SWIFTFOX: 'Swiftfox',
TESLA: 'Tesla',
@@ -228,8 +230,9 @@ export const DeviceVendor: Readonly<{
ACER: 'Acer',
ADVAN: 'Advan',
ALCATEL: 'Alcatel',
APPLE: 'Apple',
AMAZON: 'Amazon',
ANBERNIC: 'Anbernic',
APPLE: 'Apple',
ARCHOS: 'Archos',
ASUS: 'ASUS',
ATT: 'AT&T',
@@ -258,6 +261,7 @@ export const DeviceVendor: Readonly<{
LAVA: 'Lava',
LENOVO: 'Lenovo',
LG: 'LG',
LOGITECH: 'Logitech',
MEIZU: 'Meizu',
MICROMAX: 'Micromax',
MICROSOFT: 'Microsoft',
@@ -291,6 +295,7 @@ export const DeviceVendor: Readonly<{
TECNO: 'TECNO',
TESLA: 'Tesla',
ULEFONE: 'Ulefone',
VALVE: 'Valve',
VIVO: 'Vivo',
VIZIO: 'Vizio',
VODAFONE: 'Vodafone',
@@ -631,6 +636,7 @@ export const Extension: Readonly<{
},
Fetcher: {
AHREFS_SITEAUDIT: 'AhrefsSiteAudit',
AMAZON_NOVA_ACT: 'NovaAct',
ANTHROPIC_CLAUDE_USER: 'Claude-User',
ASANA: 'Asana',
BETTER_UPTIME_BOT: 'Better Uptime Bot',
@@ -708,9 +714,13 @@ export const Extension: Readonly<{
AIOHTTP: 'aiohttp',
APACHE_HTTPCLIENT: 'Apache-HttpClient',
AXIOS: 'axios',
BUN: 'Bun',
DART: 'Dart',
DENO: 'Deno',
GO_HTTP_CLIENT: 'go-http-client',
GOT: 'got',
GUZZLEHTTP: 'GuzzleHttp',
HACKNEY: 'hackney',
JAVA: 'Java',
JAVA_HTTPCLIENT: 'Java-http-client',
JSDOM: 'jsdom',
@@ -718,16 +728,19 @@ export const Extension: Readonly<{
LUA_RESTY_HTTP: 'lua-resty-http',
NEEDLE: 'Needle',
NUTCH: 'Nutch',
OKHTTP: 'OkHttp',
NODE_FETCH: 'node-fetch',
NODE_JS: 'Node.js',
NODE_SUPERAGENT: 'node-superagent',
OKHTTP: 'OkHttp',
PHP_SOAP: 'PHP-SOAP',
POSTMAN_RUNTIME: 'PostmanRuntime',
PYTHON_HTTPX: 'python-httpx',
PYTHON_URLLIB: 'python-urllib',
PYTHON_URLLIB3: 'python-urllib3',
PYTHON_REQUESTS: 'python-requests',
SCRAPY: 'Scrapy'
REST_CLIENT: 'rest-client',
SCRAPY: 'Scrapy',
UNDICI: 'undici'
}
},
DeviceVendor: {

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////
/* Enums for UAParser.js v2.0.6
/* Enums for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */

View File

@@ -3,7 +3,7 @@
// Source: /src/enums/ua-parser-enums.js
///////////////////////////////////////////////
/* Enums for UAParser.js v2.0.6
/* Enums for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -19,6 +19,7 @@ const BrowserName = Object.freeze({
AMAYA: 'Amaya',
ANDROID: 'Android Browser',
ARORA: 'Arora',
ATLAS: 'Atlas',
AVANT: 'Avant',
AVAST: 'Avast Secure Browser',
AVG: 'AVG Secure Browser',
@@ -147,6 +148,7 @@ const BrowserName = Object.freeze({
SNAPCHAT: 'Snapchat',
SOGOU_EXPLORER: 'Sogou Explorer',
SOGOU_MOBILE: 'Sogou Mobile',
STEAM: 'Steam',
SURF: 'Surf',
SWIFTFOX: 'Swiftfox',
TESLA: 'Tesla',
@@ -228,8 +230,9 @@ const DeviceVendor = Object.freeze({
ACER: 'Acer',
ADVAN: 'Advan',
ALCATEL: 'Alcatel',
APPLE: 'Apple',
AMAZON: 'Amazon',
ANBERNIC: 'Anbernic',
APPLE: 'Apple',
ARCHOS: 'Archos',
ASUS: 'ASUS',
ATT: 'AT&T',
@@ -258,6 +261,7 @@ const DeviceVendor = Object.freeze({
LAVA: 'Lava',
LENOVO: 'Lenovo',
LG: 'LG',
LOGITECH: 'Logitech',
MEIZU: 'Meizu',
MICROMAX: 'Micromax',
MICROSOFT: 'Microsoft',
@@ -291,6 +295,7 @@ const DeviceVendor = Object.freeze({
TECNO: 'TECNO',
TESLA: 'Tesla',
ULEFONE: 'Ulefone',
VALVE: 'Valve',
VIVO: 'Vivo',
VIZIO: 'Vizio',
VODAFONE: 'Vodafone',
@@ -631,6 +636,7 @@ const Extension = Object.freeze({
},
Fetcher: {
AHREFS_SITEAUDIT: 'AhrefsSiteAudit',
AMAZON_NOVA_ACT: 'NovaAct',
ANTHROPIC_CLAUDE_USER: 'Claude-User',
ASANA: 'Asana',
BETTER_UPTIME_BOT: 'Better Uptime Bot',
@@ -708,9 +714,13 @@ const Extension = Object.freeze({
AIOHTTP: 'aiohttp',
APACHE_HTTPCLIENT: 'Apache-HttpClient',
AXIOS: 'axios',
BUN: 'Bun',
DART: 'Dart',
DENO: 'Deno',
GO_HTTP_CLIENT: 'go-http-client',
GOT: 'got',
GUZZLEHTTP: 'GuzzleHttp',
HACKNEY: 'hackney',
JAVA: 'Java',
JAVA_HTTPCLIENT: 'Java-http-client',
JSDOM: 'jsdom',
@@ -718,16 +728,19 @@ const Extension = Object.freeze({
LUA_RESTY_HTTP: 'lua-resty-http',
NEEDLE: 'Needle',
NUTCH: 'Nutch',
OKHTTP: 'OkHttp',
NODE_FETCH: 'node-fetch',
NODE_JS: 'Node.js',
NODE_SUPERAGENT: 'node-superagent',
OKHTTP: 'OkHttp',
PHP_SOAP: 'PHP-SOAP',
POSTMAN_RUNTIME: 'PostmanRuntime',
PYTHON_HTTPX: 'python-httpx',
PYTHON_URLLIB: 'python-urllib',
PYTHON_URLLIB3: 'python-urllib3',
PYTHON_REQUESTS: 'python-requests',
SCRAPY: 'Scrapy'
REST_CLIENT: 'rest-client',
SCRAPY: 'Scrapy',
UNDICI: 'undici'
}
},
DeviceVendor: {

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////
/* Extensions for UAParser.js v2.0.6
/* Extensions for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */

View File

@@ -3,7 +3,7 @@
// Source: /src/extensions/ua-parser-extensions.js
///////////////////////////////////////////////
/* Extensions for UAParser.js v2.0.6
/* Extensions for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -262,7 +262,7 @@ const Emails = Object.freeze({
const Fetchers = Object.freeze({
browser : [
[
// Asana / Bitlybot / Better Uptime / BingPreview / Blueno / Cohere-AI / HubSpot Page Fetcher / kakaotalk-scrap / Mastodon / MicrosoftPreview / Pinterestbot / Redditbot / Rogerbot / SiteAuditBot / Telegrambot / Twitterbot / UptimeRobot
// Asana / Bitlybot / Better Uptime / BingPreview / Blueno / Cohere-AI / HubSpot Page Fetcher / kakaotalk-scrap / Mastodon / MicrosoftPreview / Pinterestbot / Redditbot / Rogerbot / SiteAuditBot / Telegrambot / Twitterbot / UptimeRobot / WhatsApp
// AhrefsSiteAudit - https://ahrefs.com/robot/site-audit
// Buffer Link Preview Bot - https://scraper.buffer.com/about/bots/link-preview-bot
// ChatGPT-User - https://platform.openai.com/docs/plugins/bot
@@ -272,19 +272,19 @@ const Fetchers = Object.freeze({
// Perplexity-User - https://docs.perplexity.ai/guides/bots
// MistralAI-User - https://docs.mistral.ai/robots/
// Yandex Bots - https://yandex.com/bots
/(asana|ahrefssiteaudit|(?:bing|microsoft)preview|blueno|(?:chatgpt|claude|mistralai|perplexity)-user|cohere-ai|hubspot page fetcher|mastodon|(?:bitly|bufferlinkpreview|discord|duckassist|linkedin|pinterest|reddit|roger|siteaudit|twitter|uptimero|zoom)bot|google-site-verification|iframely|kakaotalk-scrap|meta-externalfetcher|y!?j-dlc|yandex(?:calendar|direct(?:dyn)?|fordomain|pagechecker|searchshop)|yadirectfetcher)\/([\w\.]+)/i,
/(asana|ahrefssiteaudit|(?:bing|microsoft)preview|blueno|(?:chatgpt|claude|mistralai|perplexity)-user|cohere-ai|hubspot page fetcher|mastodon|(?:bitly|bufferlinkpreview|discord|duckassist|linkedin|pinterest|reddit|roger|siteaudit|twitter|uptimero|zoom)bot|google-site-verification|iframely|kakaotalk-scrap|meta-externalfetcher|y!?j-dlc|yandex(?:calendar|direct(?:dyn)?|fordomain|pagechecker|searchshop)|yadirectfetcher|whatsapp)\/([\w\.]+)/i,
// Bluesky
/(bluesky) cardyb\/([\w\.]+)/i,
// Nova Act - https://github.com/aws/nova-act
/agent-(novaact)\/([\w\.]+)/i,
// Skype
/(skypeuripreview) preview\/([\w\.]+)/i,
// Slackbot - https://api.slack.com/robots
/(slack(?:bot)?(?:-imgproxy|-linkexpanding)?) ([\w\.]+)/i,
// WhatsApp
/(whatsapp)\/([\w\.]+)/i
/(slack(?:bot)?(?:-imgproxy|-linkexpanding)?) ([\w\.]+)/i
],
[NAME, VERSION, [TYPE, FETCHER]],
@@ -393,13 +393,15 @@ const MediaPlayers = Object.freeze({
const Libraries = Object.freeze({
browser : [
// Apache-HttpClient/Axios/go-http-client/got/GuzzleHttp/Java[-HttpClient]/jsdom/libwww-perl/lua-resty-http/Needle/node-fetch/OkHttp/PHP-SOAP/PostmanRuntime/python-urllib/python-requests/Scrapy/superagent
[
/^(apache-httpclient|axios|(?:go|java)-http-client|got|guzzlehttp|java|libwww-perl|lua-resty-http|needle|node-(?:fetch|superagent)|okhttp|php-soap|postmanruntime|python-(?:httpx|urllib[23]?|requests)|scrapy)\/([\w\.]+)/i,
// Apache-HttpClient/Axios/Bun/Dart/go-http-client/got/GuzzleHttp/hackney/Java[-HttpClient]/jsdom/libwww-perl/lua-resty-http/Needle/Node.js/node-fetch/OkHttp/PHP-SOAP/PostmanRuntime/python-urllib/python-requests/rest-client/Scrapy/superagent
/^(apache-httpclient|axios|bun|dart|deno|(?:go|java)-http-client|got|guzzlehttp|hackney|java|libwww-perl|lua-resty-http|needle|node(?:\.js|-fetch|-superagent)|okhttp|php-soap|postmanruntime|python-(?:httpx|urllib[23]?|requests)|rest-client|scrapy)\/([\w\.]+)/i,
/(adobeair|aiohttp|jsdom)\/([\w\.]+)/i,
/(nutch)-([\w\.-]+)(\(|$)/i,
/\((java)\/([\w\.]+)/i
], [NAME, VERSION, [TYPE, LIBRARY]]
], [NAME, VERSION, [TYPE, LIBRARY]], [
/(node-fetch|undici)/i
], [NAME, [TYPE, LIBRARY]]
]
});

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////
/* Helpers for UAParser.js v2.0.6
/* Helpers for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -11,7 +11,7 @@ const { UAParser } = require('../main/ua-parser');
const { EngineName } = require('../enums/ua-parser-enums');
const { getDeviceVendor: _getDeviceVendor, isAppleSilicon: _isAppleSilicon } = require('../device-detection/device-detection');
const { isBot: _isBot, isAICrawler } = require('../bot-detection/bot-detection');
const { isStandalonePWA: _isStandalonePWA } = require('../browser-detection/browser-detection');
const { isChromeFamily: _isChromeFamily, isElectron: _isElectron, isStandalonePWA: _isStandalonePWA } = require('../browser-detection/browser-detection');
const { isFromEU: _isFromEU } = require('../browser-detection/browser-detection');
const { isFrozenUA } = require('ua-is-frozen');
@@ -38,13 +38,12 @@ const isBot = _isBot;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isChromeFamily = val => !!((typeof val === 'string' ? new UAParser(val).getEngine() : val.engine)?.is(EngineName.BLINK));
const isChromeFamily = _isChromeFamily;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
const isElectron = () => _isElectron;
/**
* @deprecated Moved to `browser-detection` submodule

View File

@@ -3,7 +3,7 @@
// Source: /src/helpers/ua-parser-helpers.js
///////////////////////////////////////////////
/* Helpers for UAParser.js v2.0.6
/* Helpers for UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -15,9 +15,9 @@ import { UAParser } from '../main/ua-parser.mjs';
import { EngineName } from '../enums/ua-parser-enums.mjs';
import { getDeviceVendor: _getDeviceVendor, isAppleSilicon: _isAppleSilicon } from '../device-detection/device-detection.mjs';
import { isBot: _isBot, isAICrawler } from '../bot-detection/bot-detection.mjs';
import { isFromEU } from 'detect-europe-js';
import { isChromeFamily: _isChromeFamily, isElectron: _isElectron, isStandalonePWA: _isStandalonePWA } from '../browser-detection/browser-detection.mjs';
import { isFromEU: _isFromEU } from '../browser-detection/browser-detection.mjs';
import { isFrozenUA } from 'ua-is-frozen';
import { isStandalonePWA } from 'is-standalone-pwa';
/**
* @deprecated Moved to `device-detection` submodule
@@ -39,10 +39,25 @@ const isAIBot = isAICrawler;
*/
const isBot = _isBot;
const isChromeFamily = val => !!((typeof val === 'string' ? new UAParser(val).getEngine() : val.engine)?.is(EngineName.BLINK));
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isChromeFamily = _isChromeFamily;
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isElectron = () => _isElectron;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isFromEU = _isFromEU;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isStandalonePWA = _isStandalonePWA;
export {
getDeviceVendor,

View File

@@ -1,4 +1,4 @@
// Type definitions for UAParser.js v2.0.6
// Type definitions for UAParser.js v2.0.7
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>

View File

@@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////////////
/* UAParser.js v2.0.6
/* UAParser.js v2.0.7
Copyright © 2012-2025 Faisal Salman <f@faisalman.com>
AGPLv3 License *//*
Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
@@ -19,7 +19,7 @@
// Constants
/////////////
var LIBVERSION = '2.0.6',
var LIBVERSION = '2.0.7',
UA_MAX_LENGTH = 500,
USER_AGENT = 'user-agent',
EMPTY = '',

View File

@@ -3,7 +3,7 @@
// Source: /src/main/ua-parser.js
/////////////////////////////////////////////////////////////////////////////////
/* UAParser.js v2.0.6
/* UAParser.js v2.0.7
Copyright © 2012-2025 Faisal Salman <f@faisalman.com>
AGPLv3 License *//*
Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
@@ -21,7 +21,7 @@
// Constants
/////////////
var LIBVERSION = '2.0.6',
var LIBVERSION = '2.0.7',
UA_MAX_LENGTH = 500,
USER_AGENT = 'user-agent',
EMPTY = '',
@@ -378,8 +378,8 @@
/(?:ms|\()(ie) ([\w\.]+)/i, // Internet Explorer
// Blink/Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon/LG Browser/Otter/qutebrowser/Dooble/Palemoon
/(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon|otter|dooble|(?:lg |qute)browser|palemoon)\/([-\w\.]+)/i,
// Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(atlas|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon|otter|dooble|(?:lg |qute)browser|palemoon)\/([-\w\.]+)/i,
// Atlas/Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(heytap|ovi|115|surf|qwant)browser\/([\d\.]+)/i, // HeyTap/Ovi/115/Surf
/(qwant)(?:ios|mobile)\/([\d\.]+)/i, // Qwant
/(ecosia|weibo)(?:__| \w+@)([\d\.]+)/i // Ecosia/Weibo
@@ -434,7 +434,7 @@
/(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w\.]+))/i, // Tesla
/m?(qqbrowser|2345(?=browser|chrome|explorer))\w*[\/ ]?v?([\w\.]+)/i // QQ/2345
], [NAME, VERSION], [
/(lbbrowser|rekonq)/i // LieBao Browser/Rekonq
/(lbbrowser|rekonq|steam(?= (clie|tenf|gameo)))/i // LieBao Browser/Rekonq/Steam
], [NAME], [
/ome\/([\w\.]+) \w* ?(iron) saf/i, // Iron
/ome\/([\w\.]+).+qihu (360)[es]e/i // 360
@@ -600,12 +600,12 @@
/\b(?:xiao)?((?:red)?mi[-_ ]?pad[\w- ]*)(?: bui|\))/i // Mi Pad tablets
],[[MODEL, /_/g, ' '], [VENDOR, XIAOMI], [TYPE, TABLET]], [
/\b(poco[\w ]+|m2\d{3}j\d\d[a-z]{2})(?: bui|\))/i, // Xiaomi POCO
/\b; (\w+) build\/hm\1/i, // Xiaomi Hongmi 'numeric' models
/\b(hm[-_ ]?note?[_ ]?(?:\d\w)?) bui/i, // Xiaomi Hongmi
/\b(redmi[\-_ ]?(?:note|k)?[\w_ ]+)(?: bui|\))/i, // Xiaomi Redmi
/oid[^\)]+; (m?[12][0-389][01]\w{3,6}[c-y])( bui|; wv|\))/i, // Xiaomi Redmi 'numeric' models
/\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note|max|cc)?[_ ]?(?:\d{0,2}\w?)[_ ]?(?:plus|se|lite|pro)?( 5g|lte)?)(?: bui|\))/i, // Xiaomi Mi
// Xiaomi Redmi / POCO / Black Shark / Qin
/oid[^\)]+; (redmi[\-_ ]?(?:note|k)?[\w_ ]+|m?[12]\d[01]\d\w{3,6}|poco[\w ]+|(shark )?\w{3}-[ah]0|qin ?[1-3](s\+|ultra| pro)?)( bui|; wv|\))/i,
// Xiaomi Mi
/\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note|max|cc)?[_ ]?(?:\d{0,2}\w?)[_ ]?(?:plus|se|lite|pro)?( 5g|lte)?)(?: bui|\))/i,
/ ([\w ]+) miui\/v?\d/i
], [[MODEL, /_/g, ' '], [VENDOR, XIAOMI], [TYPE, MOBILE]], [
@@ -854,10 +854,10 @@
], [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]], [
/(ouya)/i, // Ouya
/(nintendo) (\w+)/i, // Nintendo
/(retroid) (pocket ([^\)]+))/i // Retroid Pocket
], [VENDOR, MODEL, [TYPE, CONSOLE]], [
/droid.+; (shield)( bui|\))/i // Nvidia Portable
], [MODEL, [VENDOR, NVIDIA], [TYPE, CONSOLE]], [
/(retroid) (pocket ([^\)]+))/i, // Retroid Pocket
/(valve).+(steam deck)/i,
/droid.+; ((shield|rgcube|gr0006))( bui|\))/i // Nvidia Portable/Anbernic/Logitech
], [[VENDOR, strMapper, { 'Nvidia': 'Shield', 'Anbernic': 'RGCUBE', 'Logitech': 'GR0006' }], MODEL, [TYPE, CONSOLE]], [
///////////////////
// WEARABLES
@@ -866,7 +866,7 @@
/\b(sm-[lr]\d\d[0156][fnuw]?s?|gear live)\b/i // Samsung Galaxy Watch
], [MODEL, [VENDOR, SAMSUNG], [TYPE, WEARABLE]], [
/((pebble))app/i, // Pebble
/(asus|google|lg|oppo) ((pixel |zen)?watch[\w ]*)( bui|\))/i // Asus ZenWatch / LG Watch / Pixel Watch
/(asus|google|lg|oppo|xiaomi) ((pixel |zen)?watch[\w ]*)( bui|\))/i // Asus ZenWatch / LG Watch / Pixel Watch / Xiaomi Watch
], [VENDOR, MODEL, [TYPE, WEARABLE]], [
/(ow(?:19|20)?we?[1-3]{1,3})/i // Oppo Watch
], [MODEL, [VENDOR, OPPO], [TYPE, WEARABLE]], [
@@ -970,7 +970,8 @@
// iOS/macOS
/[adehimnop]{4,7}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
/(?:ios;fbsv|ios(?=.+ip(?:ad|hone))|ip(?:ad|hone)(?: |.+i(?:pad)?)os)[\/ ]([\w\.]+)/i,
/(?:ios;fbsv|ios(?=.+ip(?:ad|hone)|.+apple ?tv)|ip(?:ad|hone)(?: |.+i(?:pad)?)os|apple ?tv.+ios)[\/ ]([\w\.]+)/i,
/\btvos ?([\w\.]+)/i,
/cfnetwork\/.+darwin/i
], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [
/(mac os x) ?([\w\. ]*)/i,
@@ -1144,25 +1145,25 @@
};
}
if (!NAVIGATOR_UADATA) {
IData.prototype.then = function (cb) {
var that = this;
var IDataResolve = function () {
for (var prop in that) {
if (that.hasOwnProperty(prop)) {
this[prop] = that[prop];
}
IData.prototype.then = function (cb) {
var that = this;
var IDataResolve = function () {
for (var prop in that) {
if (that.hasOwnProperty(prop)) {
this[prop] = that[prop];
}
};
IDataResolve.prototype = {
is : IData.prototype.is,
toString : IData.prototype.toString
};
var resolveData = new IDataResolve();
cb(resolveData);
return resolveData;
}
};
}
IDataResolve.prototype = {
is : IData.prototype.is,
toString : IData.prototype.toString,
withClientHints : IData.prototype.withClientHints,
withFeatureCheck : IData.prototype.withFeatureCheck
};
var resolveData = new IDataResolve();
cb(resolveData);
return resolveData;
};
return new IData();
};
@@ -1194,189 +1195,6 @@
}
function UAItem (itemType, ua, rgxMap, uaCH) {
this.get = function (prop) {
if (!prop) return this.data;
return this.data.hasOwnProperty(prop) ? this.data[prop] : undefined;
};
this.set = function (prop, val) {
this.data[prop] = val;
return this;
};
this.setCH = function (ch) {
this.uaCH = ch;
return this;
};
this.detectFeature = function () {
if (NAVIGATOR && NAVIGATOR.userAgent == this.ua) {
switch (this.itemType) {
case BROWSER:
// Brave-specific detection
if (NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == TYPEOF.FUNCTION) {
this.set(NAME, 'Brave');
}
break;
case DEVICE:
// Chrome-specific detection: check for 'mobile' value of navigator.userAgentData
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(MODEL) == 'Macintosh' && NAVIGATOR && typeof NAVIGATOR.standalone !== TYPEOF.UNDEFINED && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) {
this.set(MODEL, 'iPad')
.set(TYPE, TABLET);
}
break;
case OS:
// Chrome-specific detection: check for 'platform' value of navigator.userAgentData
if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[PLATFORM]) {
this.set(NAME, NAVIGATOR_UADATA[PLATFORM]);
}
break;
case RESULT:
var data = this.data;
var detect = function (itemType) {
return data[itemType]
.getItem()
.detectFeature()
.get();
};
this.set(BROWSER, detect(BROWSER))
.set(CPU, detect(CPU))
.set(DEVICE, detect(DEVICE))
.set(ENGINE, detect(ENGINE))
.set(OS, detect(OS));
}
}
return this;
};
this.parseUA = function () {
if (this.itemType != RESULT) {
rgxMapper.call(this.data, this.ua, this.rgxMap);
}
switch (this.itemType) {
case BROWSER:
this.set(MAJOR, majorize(this.get(VERSION)));
break;
case OS:
if (this.get(NAME) == 'iOS' && this.get(VERSION) == '18.6') {
// Based on the assumption that iOS version is tightly coupled with Safari version
var realVersion = /\) Version\/([\d\.]+)/.exec(this.ua); // Get Safari version
if (realVersion && parseInt(realVersion[1].substring(0,2), 10) >= 26) {
this.set(VERSION, realVersion[1]); // Set as iOS version
}
}
break;
}
return this;
};
this.parseCH = function () {
var uaCH = this.uaCH,
rgxMap = this.rgxMap;
switch (this.itemType) {
case BROWSER:
case ENGINE:
var brands = uaCH[FULLVERLIST] || uaCH[BRANDS], prevName;
if (brands) {
for (var i=0; i<brands.length; i++) {
var brandName = brands[i].brand || brands[i],
brandVersion = brands[i].version;
if (this.itemType == BROWSER &&
!/not.a.brand/i.test(brandName) &&
(!prevName ||
(/Chrom/.test(prevName) && brandName != CHROMIUM) ||
(prevName == EDGE && /WebView2/.test(brandName))
)) {
brandName = strMapper(brandName, browserHintsMap);
prevName = this.get(NAME);
if (!(prevName && !/Chrom/.test(prevName) && /Chrom/.test(brandName))) {
this.set(NAME, brandName)
.set(VERSION, brandVersion)
.set(MAJOR, majorize(brandVersion));
}
prevName = brandName;
}
if (this.itemType == ENGINE && brandName == CHROMIUM) {
this.set(VERSION, brandVersion);
}
}
}
break;
case CPU:
var archName = uaCH[ARCHITECTURE];
if (archName) {
if (archName && uaCH[BITNESS] == '64') archName += '64';
rgxMapper.call(this.data, archName + ';', rgxMap);
}
break;
case DEVICE:
if (uaCH[MOBILE]) {
this.set(TYPE, MOBILE);
}
if (uaCH[MODEL]) {
this.set(MODEL, uaCH[MODEL]);
if (!this.get(TYPE) || !this.get(VENDOR)) {
var reParse = {};
rgxMapper.call(reParse, 'droid 9; ' + uaCH[MODEL] + ')', rgxMap);
if (!this.get(TYPE) && !!reParse.type) {
this.set(TYPE, reParse.type);
}
if (!this.get(VENDOR) && !!reParse.vendor) {
this.set(VENDOR, reParse.vendor);
}
}
}
if (uaCH[FORMFACTORS]) {
var ff;
if (typeof uaCH[FORMFACTORS] !== 'string') {
var idx = 0;
while (!ff && idx < uaCH[FORMFACTORS].length) {
ff = strMapper(uaCH[FORMFACTORS][idx++], formFactorsMap);
}
} else {
ff = strMapper(uaCH[FORMFACTORS], formFactorsMap);
}
this.set(TYPE, ff);
}
break;
case OS:
var osName = uaCH[PLATFORM];
if(osName) {
var osVersion = uaCH[PLATFORMVER];
if (osName == WINDOWS) osVersion = (parseInt(majorize(osVersion), 10) >= 13 ? '11' : '10');
this.set(NAME, osName)
.set(VERSION, osVersion);
}
// Xbox-Specific Detection
if (this.get(NAME) == WINDOWS && uaCH[MODEL] == 'Xbox') {
this.set(NAME, 'Xbox')
.set(VERSION, undefined);
}
break;
case RESULT:
var data = this.data;
var parse = function (itemType) {
return data[itemType]
.getItem()
.setCH(uaCH)
.parseCH()
.get();
};
this.set(BROWSER, parse(BROWSER))
.set(CPU, parse(CPU))
.set(DEVICE, parse(DEVICE))
.set(ENGINE, parse(ENGINE))
.set(OS, parse(OS));
}
return this;
};
setProps.call(this, [
['itemType', itemType],
['ua', ua],
@@ -1384,10 +1202,191 @@
['rgxMap', rgxMap],
['data', createIData(this, itemType)]
]);
return this;
}
UAItem.prototype.get = function (prop) {
if (!prop) return this.data;
return this.data.hasOwnProperty(prop) ? this.data[prop] : undefined;
};
UAItem.prototype.set = function (prop, val) {
this.data[prop] = val;
return this;
};
UAItem.prototype.setCH = function (ch) {
this.uaCH = ch;
return this;
};
UAItem.prototype.detectFeature = function () {
if (NAVIGATOR && NAVIGATOR.userAgent == this.ua) {
switch (this.itemType) {
case BROWSER:
// Brave-specific detection
if (NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == TYPEOF.FUNCTION) {
this.set(NAME, 'Brave');
}
break;
case DEVICE:
// Chrome-specific detection: check for 'mobile' value of navigator.userAgentData
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(MODEL) == 'Macintosh' && NAVIGATOR && typeof NAVIGATOR.standalone !== TYPEOF.UNDEFINED && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) {
this.set(MODEL, 'iPad')
.set(TYPE, TABLET);
}
break;
case OS:
// Chrome-specific detection: check for 'platform' value of navigator.userAgentData
if (!this.get(NAME) && NAVIGATOR_UADATA && NAVIGATOR_UADATA[PLATFORM]) {
this.set(NAME, NAVIGATOR_UADATA[PLATFORM]);
}
break;
case RESULT:
var data = this.data;
var detect = function (itemType) {
return data[itemType]
.getItem()
.detectFeature()
.get();
};
this.set(BROWSER, detect(BROWSER))
.set(CPU, detect(CPU))
.set(DEVICE, detect(DEVICE))
.set(ENGINE, detect(ENGINE))
.set(OS, detect(OS));
}
}
return this;
};
UAItem.prototype.parseUA = function () {
if (this.itemType != RESULT) {
rgxMapper.call(this.data, this.ua, this.rgxMap);
}
switch (this.itemType) {
case BROWSER:
this.set(MAJOR, majorize(this.get(VERSION)));
break;
case OS:
if (this.get(NAME) == 'iOS' && this.get(VERSION) == '18.6') {
// Based on the assumption that iOS version is tightly coupled with Safari version
var realVersion = /\) Version\/([\d\.]+)/.exec(this.ua); // Get Safari version
if (realVersion && parseInt(realVersion[1].substring(0,2), 10) >= 26) {
this.set(VERSION, realVersion[1]); // Set as iOS version
}
}
break;
}
return this;
};
UAItem.prototype.parseCH = function () {
var uaCH = this.uaCH,
rgxMap = this.rgxMap;
switch (this.itemType) {
case BROWSER:
case ENGINE:
var brands = uaCH[FULLVERLIST] || uaCH[BRANDS], prevName;
if (brands) {
for (var i=0; i<brands.length; i++) {
var brandName = brands[i].brand || brands[i],
brandVersion = brands[i].version;
if (this.itemType == BROWSER &&
!/not.a.brand/i.test(brandName) &&
(!prevName ||
(/Chrom/.test(prevName) && brandName != CHROMIUM) ||
(prevName == EDGE && /WebView2/.test(brandName))
)) {
brandName = strMapper(brandName, browserHintsMap);
prevName = this.get(NAME);
if (!(prevName && !/Chrom/.test(prevName) && /Chrom/.test(brandName))) {
this.set(NAME, brandName)
.set(VERSION, brandVersion)
.set(MAJOR, majorize(brandVersion));
}
prevName = brandName;
}
if (this.itemType == ENGINE && brandName == CHROMIUM) {
this.set(VERSION, brandVersion);
}
}
}
break;
case CPU:
var archName = uaCH[ARCHITECTURE];
if (archName) {
if (archName && uaCH[BITNESS] == '64') archName += '64';
rgxMapper.call(this.data, archName + ';', rgxMap);
}
break;
case DEVICE:
if (uaCH[MOBILE]) {
this.set(TYPE, MOBILE);
}
if (uaCH[MODEL]) {
this.set(MODEL, uaCH[MODEL]);
if (!this.get(TYPE) || !this.get(VENDOR)) {
var reParse = {};
rgxMapper.call(reParse, 'droid 9; ' + uaCH[MODEL] + ')', rgxMap);
if (!this.get(TYPE) && !!reParse.type) {
this.set(TYPE, reParse.type);
}
if (!this.get(VENDOR) && !!reParse.vendor) {
this.set(VENDOR, reParse.vendor);
}
}
}
if (uaCH[FORMFACTORS]) {
var ff;
if (typeof uaCH[FORMFACTORS] !== 'string') {
var idx = 0;
while (!ff && idx < uaCH[FORMFACTORS].length) {
ff = strMapper(uaCH[FORMFACTORS][idx++], formFactorsMap);
}
} else {
ff = strMapper(uaCH[FORMFACTORS], formFactorsMap);
}
this.set(TYPE, ff);
}
break;
case OS:
var osName = uaCH[PLATFORM];
if(osName) {
var osVersion = uaCH[PLATFORMVER];
if (osName == WINDOWS) osVersion = (parseInt(majorize(osVersion), 10) >= 13 ? '11' : '10');
this.set(NAME, osName)
.set(VERSION, osVersion);
}
// Xbox-Specific Detection
if (this.get(NAME) == WINDOWS && uaCH[MODEL] == 'Xbox') {
this.set(NAME, 'Xbox')
.set(VERSION, undefined);
}
break;
case RESULT:
var data = this.data;
var parse = function (itemType) {
return data[itemType]
.getItem()
.setCH(uaCH)
.parseCH()
.get();
};
this.set(BROWSER, parse(BROWSER))
.set(CPU, parse(CPU))
.set(DEVICE, parse(DEVICE))
.set(ENGINE, parse(ENGINE))
.set(OS, parse(OS));
}
return this;
};
function UAParser (ua, extensions, headers) {
if (typeof ua === TYPEOF.OBJECT) {