Compare commits

...

51 Commits

Author SHA1 Message Date
Faisal Salman
7abb90bf3b Bump version 2.0.8 2026-01-13 15:56:31 +07:00
Hyper-Z11
f1eed9e0b0 chore: update pattern to ONLY include top-level js files in dist (#820) 2026-01-13 14:44:31 +07:00
Faisal Salman
66f38587b8 [extensions] Add new library: http.rb, Jetty, ocaml-cohttp 2026-01-13 09:38:35 +07:00
Faisal Salman
0273bf64e6 Improve browser detection: Brave 2026-01-13 09:38:35 +07:00
Faisal Salman
21186ae1c6 Add new browser: HiBrowser 2026-01-13 09:38:35 +07:00
Faisal Salman
de2ef644ec Fix #815 - Improve device detection: OnePlus device misidentified as LG tablet 2026-01-13 09:38:35 +07:00
Faisal Salman
cf4cba5d97 Add new browser: Opera Neon - https://www.operaneon.com/ 2026-01-13 09:38:35 +07:00
Faisal Salman
7fcf3f0ac4 [extensions] Add new CLI: Windows' PowerShell 2026-01-13 09:38:35 +07:00
Faisal Salman
68f8684d09 Improve OS detection: Firefox OS 2026-01-13 09:38:35 +07:00
Faisal Salman
611221bcfb Add new engine: Dillo - https://dillo-browser.org/ 2026-01-13 09:38:35 +07:00
Faisal Salman
d54474d8e3 Fix #814 - Resolve syntax error related to import renaming in ESM version build 2026-01-13 09:38:35 +07:00
Casey Grimes
bab55a28df feat(email): expand email client detection & add Outlook edition helper (#819)
* feat(email): significantly expanded email client detection to support 40+ new user agents, including Alpine, Canary Mail, FairEmail, ProtonMail Bridge, Tutanota, and The Bat!
feat(helpers): added getOutlookEdition() utility to interpret raw version strings into specific Outlook editions (e.g., distinguishing Outlook 2016 MSI vs. Click-to-Run/365).

chore(enums): added comprehensive BrowserName.Email enums for all newly supported clients.
chore(types): added TypeScript definitions for the new getOutlookEdition helper.

test(email): added comprehensive test suite covering 60+ email client user agent strings.

test(helpers): added unit tests for getOutlookEdition covering Windows (MSI/C2R) and Mac variants.

* chore: Some small updates for business logic around K-9, Yahoo Mail, Outlook

* test: Edgecase alignment and fixes
chore(deps): npm vulnerability fix in package-lock.json
chore: Updated dist builds

* Revert accidentally-removed additional code and comments

* Correct comment syntax in ua-parser-extensions.js

Fix comment formatting and clean up code.

* chore: build fix
2026-01-12 11:34:59 +07:00
giantyo26
1696b87b58 Improve existing browser detection for Tiktok (#817)
* Improve browser detection: Tiktok

* remove .mjs files changes
2026-01-12 10:58:06 +07:00
Faisal Salman
7bc177de79 Bump version 2.0.7 2025-12-09 13:15:29 +07:00
Faisal Salman
82d50451a2 Improve device detection: Xiaomi 2025-12-01 16:51:25 +07:00
Faisal Salman
96e3518e2e Fix #812 - Add support for chaining withClientHints() & withFeatureCheck() 2025-11-28 14:51:22 +07:00
Faisal Salman
f1b9a12bda Improve OS detection: identify AppleTV's tvOS as iOS 2025-11-16 14:16:17 +07:00
Faisal Salman
82801a36b6 Add new browser: Steam 2025-11-13 12:15:10 +07:00
Faisal Salman
6f35728377 Add new device vendor: Valve - https://www.valvesoftware.com 2025-11-13 12:07:49 +07:00
Faisal Salman
1d8eab09f1 Move properties inside UAItem class into shared prototype 2025-11-13 11:42:53 +07:00
Faisal Salman
5a5b321347 [extensions] Add new library: Bun, Dart, Deno, hackney, Node.js, rest-client, undici 2025-11-09 15:33:15 +07:00
Faisal Salman
e9f78ceb80 Add new device vendor: Logitech
- G-Cloud https://www.logitechg.com/en-ch/shop/p/cloud-handheld-gaming
2025-11-08 10:06:04 +07:00
Faisal Salman
38301f8803 Add new device vendor: Anbernic - https://anbernic.com/ 2025-11-06 13:42:23 +07:00
Faisal Salman
e5648826b9 [cli] Update to use extensions when parsing string input 2025-11-06 12:36:26 +07:00
Faisal Salman
a558cc1a5b [extensions][bot-detection] Add new bot: Amazon Nova Act 2025-11-03 13:11:35 +07:00
Faisal Salman
cb9b50a81a Fix import path 2025-10-27 12:50:52 +07:00
Faisal Salman
042c57cc10 [bot-detection] Add new method: isAIAssistant() to check whether user-agent is an AI assistant 2025-10-25 16:42:36 +07:00
Faisal Salman
232fb321f1 [test] Add tests for the new submodules 2025-10-25 10:28:48 +07:00
Faisal Salman
a3a7a5e377 Break some methods in helpers as new submodules: bot-detection, browser-detection, device-detection
`isAIBot()` & `isBot()` => `bot-detection`
`isChromeFamily()`, `isElectron()`, `isFromEU()`, & `isStandalonePWA()` => `browser-detection`
`getDeviceVendor()` & `isAppleSilicon()` => `device-detection`
2025-10-25 09:51:42 +07:00
Faisal Salman
2d8c8fa142 Fix #809 - Detect OpenAI's Atlas browser
https://openai.com/index/introducing-chatgpt-atlas/
2025-10-24 22:59:48 +07:00
Faisal Salman
d84ba1888b Update image link in README 2025-10-13 20:38:26 +07:00
Faisal Salman
061cf0e90f Bump version 2.0.6 2025-10-10 10:00:44 +07:00
Faisal Salman
2882014f0e Add new inApp browser: Bing 2025-10-09 11:34:03 +07:00
Faisal Salman
3eea0643c5 Add new device vendor: Wiko - https://world.wikomobile.com/ 2025-10-08 21:51:36 +07:00
Faisal Salman
5f1ed83225 [chore] Update CLI import & unit test 2025-10-08 13:14:29 +07:00
Faisal Salman
5349bb52ed Improve device detection: BlackBerry, Huawei, Xiaomi 2025-10-08 11:12:29 +07:00
Faisal Salman
9ba4d2b207 Add new device vendor: Hisense - https://global.hisense.com/ 2025-10-06 22:16:01 +07:00
Faisal Salman
4c935c0139 Improve device detection: Nokia 2025-10-06 22:07:35 +07:00
Sébastien Règne
ae7b5e15e5 chore: Replace Undici by native Headers (#805) 2025-10-06 20:59:29 +07:00
Faisal Salman
44165a6e01 Improve CPU detection: 68k 2025-10-06 11:59:55 +07:00
Faisal Salman
fc5125042c Improve browser detection: Mozilla, Pale Moon 2025-10-06 11:59:39 +07:00
Faisal Salman
4e6259ad7f [feat] Add new CLI feature: processing batch user-agent data from file and output as JSON 2025-10-06 11:12:14 +07:00
Faisal Salman
b3bc89c463 [fix] setUA(): remove trailing space from user-agent string 2025-10-05 21:03:27 +07:00
Faisal Salman
5749302c47 [chore] Rename constants 2025-10-05 13:55:18 +07:00
Faisal Salman
6565d24567 Improve OS detection: iOS 26 2025-10-05 13:53:43 +07:00
Faisal Salman
b3281b7c12 [extensions] Add new crawler: Qwantbot-news, SurdotlyBot, Swiftbot 2025-10-01 12:19:46 +07:00
Faisal Salman
bd6bb216bd [extensions] Add new fetcher: Discordbot, KeybaseBot, Slackbot, Slackbot-LinkExpanding, Slack-ImgProxy, Twitterbot 2025-10-01 11:25:26 +07:00
Faisal Salman
e7e7aaad4c Add new browser: Qwant 2025-10-01 11:11:39 +07:00
Faisal Salman
95d2b151a3 Improve OS detection: iOS 2025-09-30 19:37:13 +07:00
Faisal Salman
737cdd4d40 Improve device detection: iPad 2025-09-29 13:02:37 +07:00
Faisal Salman
74064b0cac Add featured sponsors in README 2025-09-27 20:20:51 +07:00
67 changed files with 4883 additions and 1387 deletions

View File

@@ -23,8 +23,12 @@
- **Support for Custom/Predefined Extensions:**
- Pass custom regexes or predefined extensions as a list to `UAParser()`
- **Support for CLI Parsing:**
- Parse a user-agent directly from the command line using `npx ua-parser-js "[User-Agent]"`
- **Support for CLI Processing:**
- Directly parse user-agent strings from the command line:
`npx ua-parser-js "<User-Agent>"`
- Process batch data from files:
`npx ua-parser-js --input-file=log.txt >> result.json` or
`npx ua-parser-js --input-file=log.txt --output-file=result.json`
- **Enhanced Detection with Client Hints:**
- `withClientHints()`: Improves detection accuracy by leveraging client hints
@@ -50,21 +54,81 @@
- `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.8
- Resolve syntax error related to import renaming in ESM build
- Add new browser: HiBrowser, Opera Neon
- Add new engine: Dillo
- Improve browser detection: Brave, TikTok
- Improve device detection: OnePlus
- Improve OS detection: Firefox OS
- `extensions` submodule:
- Add new CLI: PowerShell
- Add new email: Alpine, Android, AquaMail, Balsa, Barca, Canary, Claws Mail, eM Client, Eudora, FairEmail, Geary, Gnus, Horde::IMP, Lotus-Notes, IncrediMail, K-9 Mail, Mailbird, MailMate, Mailspring, Mutt, Newton, Nine, NylasMail, Outlook-Express, Pegasus Mail, PocoMail, Postbox, ProtonMail Bridge, Quala, R2Mail2, Rainloop, Roundcube Webmail, SamsungEmail, Spicebird, SquirrelMail, Sylpheed, The Bat!, Trojita, Turnpike, tutanota-desktop, Wanderlust, Windows-Live-Mail
- Add new library: http.rb, Jetty, ocaml-cohttp
- `helpers` submodule:
- Add new method: `getOutlookEdition()` to map Outlook versions to their marketing editions
## 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
- Replace `undici` dependency with node's internal `Headers`
- Add new browser: Bing, Qwant
- Add new device vendor: Hisense, Wiko
- Improve browser detection: Mozilla, Pale Moon
- Improve CPU detection: 68k
- Improve device detection: Apple, BlackBerry, Huawei, Nokia, Xiaomi
- Improve OS detection: iOS 26
- `extensions` submodule:
- Add new fetcher: Discordbot, KeybaseBot, Slackbot, Slackbot-LinkExpanding, Slack-ImgProxy, Twitterbot
- Add new crawler: Qwantbot-news, SurdotlyBot, SwiftBot
## Version 2.0.5
- Add new browser: Zalo

View File

@@ -1,5 +1,11 @@
#### Featured Sponsors
[![https://ref.wisprflow.ai/Rxj3n8H](https://github.com/user-attachments/assets/e39fc98d-5d1b-4f7b-a355-4648e1f79a94)](https://ref.wisprflow.ai/Rxj3n8H)
---
[![https://uaparser.dev](https://raw.githubusercontent.com/faisalman/ua-parser-js/gh-pages/images/uap-header.png)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/9f30f3d4-5cfe-441c-8f86-ead7c955f940)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/a626166c-17cc-45e3-8ff6-d7e948a5ded0)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/50da50fc-7c8a-46e3-a2bc-6a8249914372)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/9f2aaff0-a9b4-4ac9-bdf3-eea8081a2582)](https://uaparser.dev)
@@ -12,7 +18,7 @@
<a href="https://www.npmjs.com/package/ua-parser-js"><img src="https://img.shields.io/npm/v/ua-parser-js.svg?logo=npm&color=red&style=for-the-badge"></a>
<a href="https://cdnjs.com/libraries/UAParser.js"><img src="https://img.shields.io/cdnjs/v/UAParser.js.svg?color=orange&style=for-the-badge"></a>
<img src="https://img.shields.io/ossf-scorecard/github.com/faisalman/ua-parser-js?label=openssf%20scorecard&style=for-the-badge">
<a target="_blank" href="https://discord.gg/stt86vmr"><img alt="Discord invite" src="https://dcbadge.limes.pink/api/server/https://discord.gg/stt86vmr"></a>
<a target="_blank" href="https://discord.com/channels/1406959509087453236/1406959509930381375"><img alt="Discord invite" src="https://dcbadge.limes.pink/api/server/https://discord.com/channels/1406959509087453236/1406959509930381375"></a>
</p>
# UAParser.js
@@ -52,7 +58,7 @@ see what's new & breaking.
<td>PRO Enterprise</td>
</tr>
<tr>
<td>Browser detection</td>
<td>Browser Detection</td>
<td><a href="#demo" title="Basic detection">⚠️</a></td>
<td></td>
<td></td>
@@ -60,7 +66,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>CPU detection</td>
<td>CPU Detection</td>
<td><a href="#demo" title="Basic detection">⚠️</a></td>
<td></td>
<td></td>
@@ -68,7 +74,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Device detection</td>
<td>Device Detection</td>
<td><a href="#demo" title="Basic detection">⚠️</a></td>
<td></td>
<td></td>
@@ -76,7 +82,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Engine detection</td>
<td>Rendering Engine Detection</td>
<td><a href="#demo" title="Basic detection">⚠️</a></td>
<td></td>
<td></td>
@@ -92,7 +98,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Bot detection</td>
<td>Enhanced+ Accuracy</td>
<td></td>
<td></td>
<td></td>
@@ -100,7 +106,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>AI Bot detection</td>
<td>Bot Detection</td>
<td></td>
<td></td>
<td></td>
@@ -108,7 +114,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Extras (Apps, Libs, Emails, Media Players, etc) detection</td>
<td>AI Detection</td>
<td></td>
<td></td>
<td></td>
@@ -116,7 +122,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Enhanced detection result</td>
<td>Extra Detections (Apps, Libs, Emails, Media Players, Crawlers, and more)</td>
<td></td>
<td></td>
<td></td>
@@ -124,7 +130,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Client Hints support</td>
<td>Client Hints Support</td>
<td></td>
<td></td>
<td></td>
@@ -132,7 +138,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>CommonJS support</td>
<td>CommonJS Support</td>
<td></td>
<td></td>
<td></td>
@@ -140,7 +146,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>ES modules support</td>
<td>ESM Support</td>
<td></td>
<td></td>
<td></td>
@@ -148,15 +154,15 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>TypeScript declarations</td>
<td><a href="#demo" title="Community version">⚠️</a></td>
<td>TypeScript Definitions</td>
<td><a href="#demo" title="Community version"></a></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>npm module available</td>
<td>npm Module Available</td>
<td></td>
<td></td>
<td></td>
@@ -164,7 +170,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Direct downloads available</td>
<td>Direct Downloads Available</td>
<td></td>
<td></td>
<td></td>
@@ -172,7 +178,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Allows commercial usage</td>
<td>Commercial Use Allowed</td>
<td></td>
<td></td>
<td></td>
@@ -180,7 +186,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Permissive (non-copyleft) license</td>
<td>Permissive (non-Copyleft) License</td>
<td></td>
<td><strong title="Copyleft license"></strong></td>
<td></td>
@@ -188,7 +194,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>No open-source obligations</td>
<td>No Open-Source Obligations</td>
<td></td>
<td><strong title="Copyleft license"></strong></td>
<td></td>
@@ -196,7 +202,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Unlimited end-products</td>
<td>Unlimited End-Products</td>
<td></td>
<td></td>
<td></td>
@@ -204,7 +210,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Unlimited deployments</td>
<td>Unlimited Deployments</td>
<td></td>
<td></td>
<td></td>
@@ -212,7 +218,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>1-year product support</td>
<td>1-year Product Support</td>
<td></td>
<td></td>
<td></td>
@@ -220,7 +226,7 @@ see what's new & breaking.
<td></td>
</tr>
<tr>
<td>Lifetime updates</td>
<td>Lifetime Updates</td>
<td></td>
<td></td>
<td></td>

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

1043
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,25 @@
{
"title": "UAParser.js",
"name": "ua-parser-js",
"version": "2.0.5",
"version": "2.0.8",
"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": [
"user-agent",
"client-hints",
"parser",
"browser",
"engine",
"os",
"device",
"cpu",
"jquery-plugin",
"ecosystem:jquery",
"ua-parser-js",
"browser-detection",
"device-detection",
"os-detection",
"bot-detection"
"bot-detection",
"ai-detection",
"app-detection",
"crawler-detection"
],
"homepage": "https://uaparser.dev",
"contributors": [
@@ -190,6 +190,21 @@
"import": "./src/main/ua-parser.mjs",
"types": "./src/main/ua-parser.d.ts"
},
"./bot-detection": {
"require": "./src/bot-detection/bot-detection.js",
"import": "./src/bot-detection/bot-detection.mjs",
"types": "./src/bot-detection/bot-detection.d.ts"
},
"./browser-detection": {
"require": "./src/browser-detection/browser-detection.js",
"import": "./src/browser-detection/browser-detection.mjs",
"types": "./src/browser-detection/browser-detection.d.ts"
},
"./device-detection": {
"require": "./src/device-detection/device-detection.js",
"import": "./src/device-detection/device-detection.mjs",
"types": "./src/device-detection/device-detection.d.ts"
},
"./enums": {
"require": "./src/enums/ua-parser-enums.js",
"import": "./src/enums/ua-parser-enums.mjs",
@@ -207,7 +222,7 @@
}
},
"files": [
"dist",
"dist/*.?(m)js",
"src"
],
"bin": "./script/cli.js",
@@ -217,23 +232,24 @@
"fuzz": "jazzer ./test/fuzz/redos.js --sync",
"test": "./script/test-all.sh",
"test:dts-lint": "tsd --typings src/main/ua-parser.d.ts --files test/static/dts-lint.ts",
"test:eslint": "eslint src && eslint script",
"test:eslint": "eslint --no-config-lookup src",
"test:jshint": "jshint src/main",
"test:lockfile-lint": "npx lockfile-lint -p package-lock.json",
"test:mocha": "mocha test/unit",
"test:lockfile-lint": "lockfile-lint -p package-lock.json",
"test:mocha": "mocha --recursive test/unit",
"test:playwright": "npx playwright install && playwright test test/e2e --browser all"
},
"dependencies": {
"detect-europe-js": "^0.1.2",
"is-standalone-pwa": "^0.1.1",
"ua-is-frozen": "^0.1.2",
"undici": "^7.12.0"
"ua-is-frozen": "^0.1.2"
},
"devDependencies": {
"@babel/parser": "7.15.8",
"@babel/traverse": "7.23.2",
"@playwright/test": "^1.49.0",
"@playwright/test": "^1.57.0",
"eslint": "^9.39.1",
"jshint": "~2.13.6",
"lockfile-lint": "^4.14.1",
"mocha": "~8.2.0",
"requirejs": "2.3.2",
"safe-regex": "^2.1.1",

View File

@@ -4,6 +4,7 @@ const fs = require('fs');
const defaultReplacements = {
mjs: [
[/(?<=const.+)(:)(?=.+require)/ig, ' as'],
[/const (.+?)\s*=\s*require\(\'\.(.+)\'\)/ig, 'import $1 from \'\.$2.mjs\''],
[/const (.+?)\s*=\s*require\(\'(.+)\'\)/ig, 'import $1 from \'$2\''],
[/module\.exports =/ig, 'export']
@@ -40,6 +41,24 @@ const files = [
...defaultReplacements.mjs
]
},
{
src : 'src/bot-detection/bot-detection.js',
dest :'src/bot-detection/bot-detection.mjs',
title : 'Generated ESM version of ua-parser-js/bot-detection',
replacements : [...defaultReplacements.mjs]
},
{
src : 'src/browser-detection/browser-detection.js',
dest :'src/browser-detection/browser-detection.mjs',
title : 'Generated ESM version of ua-parser-js/browser-detection',
replacements : [...defaultReplacements.mjs]
},
{
src : 'src/device-detection/device-detection.js',
dest :'src/device-detection/device-detection.mjs',
title : 'Generated ESM version of ua-parser-js/device-detection',
replacements : [...defaultReplacements.mjs]
},
{
src : 'src/enums/ua-parser-enums.js',
dest :'src/enums/ua-parser-enums.mjs',

View File

@@ -1,4 +1,94 @@
#!/usr/bin/env node
const UAParser = require('ua-parser-js');
console.log(JSON.stringify(process.argv.slice(2).map(ua => UAParser(ua)), null, 4));
try {
const fs = require('node:fs');
const path = require('node:path');
const { performance } = require('node:perf_hooks');
const readline = require('node:readline');
const { parseArgs } = require('node:util');
const UAParser = require('../src/main/ua-parser');
const { Bots, Emails, ExtraDevices, InApps, Vehicles } = require('../src/extensions/ua-parser-extensions');
if (!process.argv[2].startsWith('-')) {
const results = process.argv.slice(2).map(ua => UAParser(ua, [Bots, Emails, ExtraDevices, InApps, Vehicles]));
console.log(JSON.stringify(results, null, 4));
process.exit(0);
} else if (['-h', '--help'].includes(process.argv[2])) {
console.log('Usage: npx ua-parser-js <string>');
console.log(' or npx ua-parser-js --input-file <filepath> [--output-file <filepath>]');
console.log('-i, --input-file');
console.log('-o, --output-file');
process.exit(0);
} else {
const startPerf = performance.now();
const {
values: {
'input-file': inputFile,
'output-file': outputFile
},
} = parseArgs({
options: {
'input-file': { type: 'string', short: 'i' },
'output-file': { type: 'string', short: 'o' }
}
});
if (!inputFile) {
console.error('Input file must be present');
process.exit(1);
}
const inputPath = path.resolve(__dirname, inputFile);
const outputPath = outputFile ? path.resolve(__dirname, outputFile) : null;
if (!fs.existsSync(inputPath)) {
console.error(`Input file not found: ${inputPath}`);
process.exit(1);
}
const inputStream = fs.createReadStream(inputPath, 'utf8');
const rl = readline.createInterface({
input: inputStream,
crlfDelay: Infinity
});
const outputStream = outputPath ? fs.createWriteStream(outputPath, { encoding : 'utf8' }) : process.stdout;
const uap = new UAParser([Bots, Emails, ExtraDevices, InApps, Vehicles]);
let lineNumber = 0;
outputStream.write('[\n');
rl.on('line', line => {
const result = uap.setUA(line).getResult();
const json = JSON.stringify(result, null, 4);
if (lineNumber > 0) outputStream.write(',\n');
outputStream.write(json);
lineNumber++;
});
rl.on('close', () => {
outputStream.write('\n]');
if (outputPath) {
outputStream.end(() => {
const finishPerf = performance.now();
console.log(`Done!`);
console.log(`Number of lines found: ${lineNumber}`);
console.log(`Task finished in: ${(finishPerf - startPerf).toFixed(3)}ms`);
console.log(`Output written to: ${outputPath}`);
process.exit(0);
});
} else {
process.exit(0);
}
});
}
} catch (err) {
console.error(err);
process.exit(1);
}

View File

@@ -9,7 +9,7 @@ echo '
- lint js code
'
npm run test:jshint || exit 1
#npm run test:eslint || exit 1
npm run test:eslint || exit 1
echo '
- test using mocha

7
src/bot-detection/bot-detection.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
// Type definitions for bot-detection submodule of UAParser.js v2.0.8
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
export function isAIAssistant(ua: string): boolean;
export function isAICrawler(ua: string): boolean;
export function isBot(ua: string): boolean;

View File

@@ -0,0 +1,188 @@
//////////////////////////////////////////////////
/* bot-detection submodule of UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { Bots, Crawlers, Fetchers } = require('../extensions/ua-parser-extensions');
const { BrowserType, Extension } = require('../enums/ua-parser-enums');
const { Crawler, Fetcher } = Extension.BrowserName;
class BotList {
constructor(ext, prop, list) {
this.ext = ext;
this.prop = prop;
this.list = list.map(x => x.toLowerCase());
}
includes(ua) {
return this.list.includes(
(typeof ua === 'string' ?
new UAParser(ua, this.ext).getBrowser() :
ua.browser
)[this.prop]?.toLowerCase());
}
}
const BotTypes = new BotList(Bots, 'type', [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
]);
const AIAssistants = new BotList(Fetchers, 'name', [
// Amazon
Fetcher.AMAZON_NOVA_ACT,
// Anthropic
Fetcher.ANTHROPIC_CLAUDE_USER,
// Cohere
Fetcher.COHERE_AI,
// DuckDuckGo
Fetcher.DUCKDUCKGO_ASSISTBOT,
// Google
Fetcher.GOOGLE_GEMINI_DEEP_RESEARCH,
// Mistral AI
Fetcher.MISTRALAI_USER,
// OpenAI
Fetcher.OPENAI_CHATGPT_USER,
// Perplexity
Fetcher.PERPLEXITY_USER
]);
const AICrawlers = new BotList(Crawlers, 'name', [
// AI2
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]);
const isBot = ua => BotTypes.includes(ua);
const isAIAssistant = ua => AIAssistants.includes(ua);
const isAICrawler = ua => AICrawlers.includes(ua);
module.exports = {
isAIAssistant,
isAICrawler,
isBot
}

View File

@@ -0,0 +1,192 @@
// Generated ESM version of ua-parser-js/bot-detection
// DO NOT EDIT THIS FILE!
// Source: /src/bot-detection/bot-detection.js
//////////////////////////////////////////////////
/* bot-detection submodule of UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.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 {
constructor(ext, prop, list) {
this.ext = ext;
this.prop = prop;
this.list = list.map(x => x.toLowerCase());
}
includes(ua) {
return this.list.includes(
(typeof ua === 'string' ?
new UAParser(ua, this.ext).getBrowser() :
ua.browser
)[this.prop]?.toLowerCase());
}
}
const BotTypes = new BotList(Bots, 'type', [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
]);
const AIAssistants = new BotList(Fetchers, 'name', [
// Amazon
Fetcher.AMAZON_NOVA_ACT,
// Anthropic
Fetcher.ANTHROPIC_CLAUDE_USER,
// Cohere
Fetcher.COHERE_AI,
// DuckDuckGo
Fetcher.DUCKDUCKGO_ASSISTBOT,
// Google
Fetcher.GOOGLE_GEMINI_DEEP_RESEARCH,
// Mistral AI
Fetcher.MISTRALAI_USER,
// OpenAI
Fetcher.OPENAI_CHATGPT_USER,
// Perplexity
Fetcher.PERPLEXITY_USER
]);
const AICrawlers = new BotList(Crawlers, 'name', [
// AI2
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]);
const isBot = ua => BotTypes.includes(ua);
const isAIAssistant = ua => AIAssistants.includes(ua);
const isAICrawler = ua => AICrawlers.includes(ua);
export {
isAIAssistant,
isAICrawler,
isBot
}

View File

@@ -0,0 +1,10 @@
// Type definitions for browser-detection submodule of UAParser.js v2.0.8
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
import type { IResult } from "../main/ua-parser";
export function isChromeFamily(resultOrUA: IResult | string): boolean;
export function isElectron(): boolean;
export function isFromEU(): boolean;
export function isStandalonePWA(): boolean;

View File

@@ -0,0 +1,30 @@
//////////////////////////////////////////////////////
/* browser-detection submodule of UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////////
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { EngineName } = require('../enums/ua-parser-enums');
const { isStandalonePWA } = require('is-standalone-pwa');
const { isFromEU } = require('detect-europe-js');
const isChromeFamily = val => !!(
(typeof val === 'string' ?
new UAParser(val).getEngine() :
val.engine
)?.is(EngineName.BLINK));
const isElectron = () => !!(
process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
module.exports = {
isChromeFamily,
isElectron,
isFromEU,
isStandalonePWA
}

View File

@@ -0,0 +1,34 @@
// Generated ESM version of ua-parser-js/browser-detection
// DO NOT EDIT THIS FILE!
// Source: /src/browser-detection/browser-detection.js
//////////////////////////////////////////////////////
/* browser-detection submodule of UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////////
/*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' ?
new UAParser(val).getEngine() :
val.engine
)?.is(EngineName.BLINK));
const isElectron = () => !!(
process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
export {
isChromeFamily,
isElectron,
isFromEU,
isStandalonePWA
}

View File

@@ -0,0 +1,8 @@
// Type definitions for device-detection submodule of UAParser.js v2.0.8
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
import type { IResult } from "../main/ua-parser";
export function getDeviceVendor(model: string): string | undefined;
export function isAppleSilicon(resultOrUA: IResult | string): boolean;

View File

@@ -0,0 +1,43 @@
/////////////////////////////////////////////////////
/* device-detection submodule of UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
////////////////////////////////////////////////////
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { CPUArch, OSName } = require('../enums/ua-parser-enums');
const getDeviceVendor = (model) => new UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).getDevice().vendor;
const isAppleSilicon = (val) => {
const { os, cpu } = typeof val !== 'string' ? val : {
os: new UAParser(val).getOS(),
cpu: new UAParser(val).getCPU()
};
if (os.is(OSName.MACOS)) {
if (cpu.is(CPUArch.ARM)) {
return true;
} else if (typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') ||
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
return webgl
.getParameter(webgl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL)
.match(/apple m\d/i);
} catch {
return false;
}
}
}
return false;
}
module.exports = {
getDeviceVendor,
isAppleSilicon
}

View File

@@ -0,0 +1,47 @@
// Generated ESM version of ua-parser-js/device-detection
// DO NOT EDIT THIS FILE!
// Source: /src/device-detection/device-detection.js
/////////////////////////////////////////////////////
/* device-detection submodule of UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
////////////////////////////////////////////////////
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { CPUArch, OSName } from '../enums/ua-parser-enums.mjs';
const getDeviceVendor = (model) => new UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).getDevice().vendor;
const isAppleSilicon = (val) => {
const { os, cpu } = typeof val !== 'string' ? val : {
os: new UAParser(val).getOS(),
cpu: new UAParser(val).getCPU()
};
if (os.is(OSName.MACOS)) {
if (cpu.is(CPUArch.ARM)) {
return true;
} else if (typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') ||
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
return webgl
.getParameter(webgl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL)
.match(/apple m\d/i);
} catch {
return false;
}
}
}
return false;
}
export {
getDeviceVendor,
isAppleSilicon
}

View File

@@ -3,7 +3,7 @@
// Source: /src/enums/ua-parser-enums.js
///////////////////////////////////////////////
/* Enums for UAParser.js v2.0.5
/* Enums for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -19,11 +19,13 @@ export const BrowserName: Readonly<{
AMAYA: 'Amaya',
ANDROID: 'Android Browser',
ARORA: 'Arora',
ATLAS: 'Atlas',
AVANT: 'Avant',
AVAST: 'Avast Secure Browser',
AVG: 'AVG Secure Browser',
BAIDU: 'Baidu Browser',
BASILISK: 'Basilisk',
BING: 'Bing',
BLAZER: 'Blazer',
BOLT: 'Bolt',
BOWSER: 'Bowser',
@@ -64,6 +66,7 @@ export const BrowserName: Readonly<{
GOOGLE_SEARCH: 'GSA',
HELIO: 'Helio',
HEYTAP: 'HeyTap',
HIBROWSER: 'HiBrowser',
HONOR: 'Honor',
HUAWEI: 'Huawei Browser',
ICAB: 'iCab',
@@ -114,6 +117,7 @@ export const BrowserName: Readonly<{
OPERA_GX: 'Opera GX',
OPERA_MINI: 'Opera Mini',
OPERA_MOBI: 'Opera Mobi',
OPERA_NEON: 'Opera Neon',
OPERA_TABLET: 'Opera Tablet',
OPERA_TOUCH: 'Opera Touch',
OTTER: 'Otter',
@@ -129,6 +133,7 @@ export const BrowserName: Readonly<{
QUARK: 'Quark',
QUPZILLA: 'QupZilla',
QUTEBROWSER: 'qutebrowser',
QWANT: 'Qwant',
REKONQ: 'rekonq',
ROCKMELT: 'Rockmelt',
SAFARI: 'Safari',
@@ -145,6 +150,7 @@ export const BrowserName: Readonly<{
SNAPCHAT: 'Snapchat',
SOGOU_EXPLORER: 'Sogou Explorer',
SOGOU_MOBILE: 'Sogou Mobile',
STEAM: 'Steam',
SURF: 'Surf',
SWIFTFOX: 'Swiftfox',
TESLA: 'Tesla',
@@ -226,8 +232,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',
@@ -243,6 +250,7 @@ export const DeviceVendor: Readonly<{
GEEKSPHONE: 'GeeksPhone',
GENERIC: 'Generic',
GOOGLE: 'Google',
HISENSE: 'Hisense',
HMD: 'HMD',
HP: 'HP',
HTC: 'HTC',
@@ -255,6 +263,7 @@ export const DeviceVendor: Readonly<{
LAVA: 'Lava',
LENOVO: 'Lenovo',
LG: 'LG',
LOGITECH: 'Logitech',
MEIZU: 'Meizu',
MICROMAX: 'Micromax',
MICROSOFT: 'Microsoft',
@@ -288,9 +297,11 @@ export const DeviceVendor: Readonly<{
TECNO: 'TECNO',
TESLA: 'Tesla',
ULEFONE: 'Ulefone',
VALVE: 'Valve',
VIVO: 'Vivo',
VIZIO: 'Vizio',
VODAFONE: 'Vodafone',
WIKO: 'Wiko',
XBOX: 'Xbox',
XIAOMI: 'Xiaomi',
ZEBRA: 'Zebra',
@@ -307,6 +318,7 @@ export const EngineName: Readonly<{
AMAYA: 'Amaya',
ARKWEB: 'ArkWeb',
BLINK: 'Blink',
DILLO: 'Dillo',
EDGEHTML: 'EdgeHTML',
FLOW: 'Flow',
GECKO: 'Gecko',
@@ -442,6 +454,7 @@ export const Extension: Readonly<{
ELINKS: 'ELinks',
HTTPIE: 'HTTPie',
LYNX: 'Lynx',
POWERSHELL: 'PowerShell',
WGET: 'Wget'
},
Crawler: {
@@ -489,6 +502,7 @@ export const Extension: Readonly<{
DUCKDUCKGO_BOT: 'DuckDuckBot',
DUCKDUCKGO_FAVICONS_BOT: 'DuckDuckGo-Favicons-Bot',
ELASTIC: 'Elastic',
ELASTIC_SWIFTYPE_BOT: 'Swiftbot',
EXALEAD_EXABOT: 'Exabot',
FIRECRAWL_AGENT: 'FirecrawlAgent',
FREESPOKE: 'Freespoke',
@@ -538,6 +552,7 @@ export const Extension: Readonly<{
PERPLEXITY_BOT: 'PerplexityBot',
QIHOO_360_SPIDER: '360Spider',
QWANT_BOT: 'Qwantbot',
QWANT_BOT_NEWS: 'Qwantbot-news',
REPLICATE_BOT: 'Replicate-Bot',
RUNPOD_BOT: 'RunPod-Bot',
SB_INTUITIONS_BOT: 'SBIntuitionsBot',
@@ -551,6 +566,7 @@ export const Extension: Readonly<{
SOGOU_PIC_SPIDER: 'Sogou Pic Spider',
SOGOU_WEB_SPIDER: 'Sogou web spider',
STARTPAGE: 'Startpage',
SURLY_BOT: 'SurdotlyBot',
TIMPI_BOT: 'Timpibot',
TOGETHER_BOT: 'Together-Bot',
TURNITIN_BOT: 'TurnitinBot',
@@ -601,29 +617,72 @@ export const Extension: Readonly<{
},
Email: {
AIRMAIL: 'Airmail',
ALPINE: 'Alpine',
ANDROID_MAIL: 'Android',
APPLE_MAIL: 'Mail',
AQUA_MAIL: 'AquaMail',
BALSA: 'Balsa',
BARCA: 'Barca',
BLUEMAIL: 'BlueMail',
CANARY: 'Canary',
CLAWS_MAIL: 'Claws Mail',
DAUM_MAIL: 'DaumMail',
EVOLUTION: 'Evolution',
EM_CLIENT: 'eM Client',
EUDORA: 'Eudora',
EVOLUTION: 'Evolution',
FAIR_EMAIL: 'FairEmail',
FOXMAIL: 'Foxmail',
GEARY: 'Geary',
GNUS: 'Gnus',
HORDE_IMP: 'Horde::IMP',
IBM_NOTES: 'Lotus-Notes',
INCREDIMAIL: 'IncrediMail',
K9_MAIL: 'K-9 Mail',
KMAIL: 'KMail',
KMAIL2: 'kmail2',
KONTACT: 'Kontact',
MAILBIRD: 'Mailbird',
MAILMATE: 'MailMate',
MAILSPRING: 'Mailspring',
MICROSOFT_OUTLOOK: 'Microsoft Outlook',
MICROSOFT_OUTLOOK_MAC: 'MacOutlook',
MUTT: 'Mutt',
NAVER_MAILAPP: 'NaverMailApp',
NEWTON: 'Newton',
NINE: 'Nine',
NYLAS_MAIL: 'NylasMail',
OUTLOOK_EXPRESS: 'Outlook-Express',
PEGASUS_MAIL: 'Pegasus Mail',
POCOMAIL: 'PocoMail',
POLYMAIL: 'Polymail',
POSTBOX: 'Postbox',
PROTON_MAIL: 'ProtonMail',
PROTON_MAIL_BRIDGE: 'ProtonMail Bridge',
QUALA_MAIL: 'Quala',
R2MAIL2: 'R2Mail2',
RAINLOOP: 'RainLoop',
ROUNDCUBE: 'Roundcube Webmail',
SAMSUNG_EMAIL: 'SamsungEmail',
SPARK_MAIL: 'SparkDesktop',
SPARROW: 'Sparrow',
SPICEBIRD: 'Spicebird',
SQUIRRELMAIL: 'SquirrelMail',
SYLPHEED: 'Sylpheed',
THE_BAT: 'The Bat!',
THUNDERBIRD: 'Thunderbird',
YAHOO_MAIL: 'Yahoo',
TROJITA: 'Trojita',
TURNPIKE: 'Turnpike',
TUTANOTA: 'tutanota-desktop',
WANDERLUST: 'Wanderlust',
WINDOWS_LIVE_MAIL: 'Windows-Live-Mail',
YAHOO_MAIL: 'Yahoo Mail',
YAHOO_MAIL_IOS: 'Yahoo Mail',
ZIMBRA: 'Zimbra',
ZOHO_MAIL: 'ZohoMail-Desktop'
},
Fetcher: {
AHREFS_SITEAUDIT: 'AhrefsSiteAudit',
AMAZON_NOVA_ACT: 'NovaAct',
ANTHROPIC_CLAUDE_USER: 'Claude-User',
ASANA: 'Asana',
BETTER_UPTIME_BOT: 'Better Uptime Bot',
@@ -631,6 +690,7 @@ export const Extension: Readonly<{
BLUESKY: 'Bluesky',
BUFFER_LINKPREVIEWBOT: 'BufferLinkPreviewBot',
COHERE_AI: 'Cohere-AI',
DISCORD_BOT: 'Discordbot',
DUCKDUCKGO_ASSISTBOT: 'DuckAssistBot',
GOOGLE_CHROME_LIGHTHOUSE: 'Chrome-Lighthouse',
GOOGLE_FEEDFETCHER: 'FeedFetcher-Google',
@@ -643,6 +703,7 @@ export const Extension: Readonly<{
HUBSPOT_PAGE_FETCHER: 'HubSpot Page Fetcher',
IFRAMELY: 'Iframely',
KAKAOTALK_SCRAP: 'kakaotalk-scrap',
KEYBASE_BOT: 'KeybaseBot',
META_EXTERNALFETCHER: 'meta-externalfetcher',
META_WHATSAPP: 'WhatsApp',
MICROSOFT_BINGPREVIEW: 'BingPreview',
@@ -654,6 +715,9 @@ export const Extension: Readonly<{
PERPLEXITY_USER: 'Perplexity-User',
PINTEREST_BOT: 'Pinterestbot',
SEMRUSH_SITEAUDITBOT: 'SiteAuditBot',
SLACK_BOT: 'Slackbot',
SLACK_BOT_LINKEXPANDING: 'Slackbot-LinkExpanding',
SLACK_IMGPROXY: 'Slack-ImgProxy',
SNAP_URL_PREVIEW: 'Snap URL Preview',
SKYPE_URIPREVIEW: 'SkypeUriPreview',
TELEGRAM_BOT: 'TelegramBot',
@@ -663,6 +727,7 @@ export const Extension: Readonly<{
VERCEL_BOT: 'Vercelbot',
VERCEL_FLAGS: 'vercelflags',
VERCEL_TRACING: 'verceltracing',
X_TWITTERBOT: 'Twitterbot',
YANDEX_CALENDAR: 'YandexCalendar',
YANDEX_DIRECT: 'YandexDirect',
YANDEX_DIRECTDYN: 'YandexDirectDyn',
@@ -695,26 +760,36 @@ 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',
HTTP_RB: 'http.rb',
JAVA: 'Java',
JAVA_HTTPCLIENT: 'Java-http-client',
JETTY: 'Jetty',
JSDOM: 'jsdom',
LIBWWW_PERL: 'libwww-perl',
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',
OCAML_COHTTP: 'ocaml-cohttp',
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.5
/* Enums for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -15,11 +15,13 @@ const BrowserName = Object.freeze({
AMAYA: 'Amaya',
ANDROID: 'Android Browser',
ARORA: 'Arora',
ATLAS: 'Atlas',
AVANT: 'Avant',
AVAST: 'Avast Secure Browser',
AVG: 'AVG Secure Browser',
BAIDU: 'Baidu Browser',
BASILISK: 'Basilisk',
BING: 'Bing',
BLAZER: 'Blazer',
BOLT: 'Bolt',
BOWSER: 'Bowser',
@@ -60,6 +62,7 @@ const BrowserName = Object.freeze({
GOOGLE_SEARCH: 'GSA',
HELIO: 'Helio',
HEYTAP: 'HeyTap',
HIBROWSER: 'HiBrowser',
HONOR: 'Honor',
HUAWEI: 'Huawei Browser',
ICAB: 'iCab',
@@ -110,6 +113,7 @@ const BrowserName = Object.freeze({
OPERA_GX: 'Opera GX',
OPERA_MINI: 'Opera Mini',
OPERA_MOBI: 'Opera Mobi',
OPERA_NEON: 'Opera Neon',
OPERA_TABLET: 'Opera Tablet',
OPERA_TOUCH: 'Opera Touch',
OTTER: 'Otter',
@@ -125,6 +129,7 @@ const BrowserName = Object.freeze({
QUARK: 'Quark',
QUPZILLA: 'QupZilla',
QUTEBROWSER: 'qutebrowser',
QWANT: 'Qwant',
REKONQ: 'rekonq',
ROCKMELT: 'Rockmelt',
SAFARI: 'Safari',
@@ -141,6 +146,7 @@ const BrowserName = Object.freeze({
SNAPCHAT: 'Snapchat',
SOGOU_EXPLORER: 'Sogou Explorer',
SOGOU_MOBILE: 'Sogou Mobile',
STEAM: 'Steam',
SURF: 'Surf',
SWIFTFOX: 'Swiftfox',
TESLA: 'Tesla',
@@ -222,8 +228,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',
@@ -239,6 +246,7 @@ const DeviceVendor = Object.freeze({
GEEKSPHONE: 'GeeksPhone',
GENERIC: 'Generic',
GOOGLE: 'Google',
HISENSE: 'Hisense',
HMD: 'HMD',
HP: 'HP',
HTC: 'HTC',
@@ -251,6 +259,7 @@ const DeviceVendor = Object.freeze({
LAVA: 'Lava',
LENOVO: 'Lenovo',
LG: 'LG',
LOGITECH: 'Logitech',
MEIZU: 'Meizu',
MICROMAX: 'Micromax',
MICROSOFT: 'Microsoft',
@@ -284,9 +293,11 @@ const DeviceVendor = Object.freeze({
TECNO: 'TECNO',
TESLA: 'Tesla',
ULEFONE: 'Ulefone',
VALVE: 'Valve',
VIVO: 'Vivo',
VIZIO: 'Vizio',
VODAFONE: 'Vodafone',
WIKO: 'Wiko',
XBOX: 'Xbox',
XIAOMI: 'Xiaomi',
ZEBRA: 'Zebra',
@@ -303,6 +314,7 @@ const EngineName = Object.freeze({
AMAYA: 'Amaya',
ARKWEB: 'ArkWeb',
BLINK: 'Blink',
DILLO: 'Dillo',
EDGEHTML: 'EdgeHTML',
FLOW: 'Flow',
GECKO: 'Gecko',
@@ -438,6 +450,7 @@ const Extension = Object.freeze({
ELINKS: 'ELinks',
HTTPIE: 'HTTPie',
LYNX: 'Lynx',
POWERSHELL: 'PowerShell',
WGET: 'Wget'
},
Crawler: {
@@ -485,6 +498,7 @@ const Extension = Object.freeze({
DUCKDUCKGO_BOT: 'DuckDuckBot',
DUCKDUCKGO_FAVICONS_BOT: 'DuckDuckGo-Favicons-Bot',
ELASTIC: 'Elastic',
ELASTIC_SWIFTYPE_BOT: 'Swiftbot',
EXALEAD_EXABOT: 'Exabot',
FIRECRAWL_AGENT: 'FirecrawlAgent',
FREESPOKE: 'Freespoke',
@@ -534,6 +548,7 @@ const Extension = Object.freeze({
PERPLEXITY_BOT: 'PerplexityBot',
QIHOO_360_SPIDER: '360Spider',
QWANT_BOT: 'Qwantbot',
QWANT_BOT_NEWS: 'Qwantbot-news',
REPLICATE_BOT: 'Replicate-Bot',
RUNPOD_BOT: 'RunPod-Bot',
SB_INTUITIONS_BOT: 'SBIntuitionsBot',
@@ -547,6 +562,7 @@ const Extension = Object.freeze({
SOGOU_PIC_SPIDER: 'Sogou Pic Spider',
SOGOU_WEB_SPIDER: 'Sogou web spider',
STARTPAGE: 'Startpage',
SURLY_BOT: 'SurdotlyBot',
TIMPI_BOT: 'Timpibot',
TOGETHER_BOT: 'Together-Bot',
TURNITIN_BOT: 'TurnitinBot',
@@ -597,29 +613,72 @@ const Extension = Object.freeze({
},
Email: {
AIRMAIL: 'Airmail',
ALPINE: 'Alpine',
ANDROID_MAIL: 'Android',
APPLE_MAIL: 'Mail',
AQUA_MAIL: 'AquaMail',
BALSA: 'Balsa',
BARCA: 'Barca',
BLUEMAIL: 'BlueMail',
CANARY: 'Canary',
CLAWS_MAIL: 'Claws Mail',
DAUM_MAIL: 'DaumMail',
EVOLUTION: 'Evolution',
EM_CLIENT: 'eM Client',
EUDORA: 'Eudora',
EVOLUTION: 'Evolution',
FAIR_EMAIL: 'FairEmail',
FOXMAIL: 'Foxmail',
GEARY: 'Geary',
GNUS: 'Gnus',
HORDE_IMP: 'Horde::IMP',
IBM_NOTES: 'Lotus-Notes',
INCREDIMAIL: 'IncrediMail',
K9_MAIL: 'K-9 Mail',
KMAIL: 'KMail',
KMAIL2: 'kmail2',
KONTACT: 'Kontact',
MAILBIRD: 'Mailbird',
MAILMATE: 'MailMate',
MAILSPRING: 'Mailspring',
MICROSOFT_OUTLOOK: 'Microsoft Outlook',
MICROSOFT_OUTLOOK_MAC: 'MacOutlook',
MUTT: 'Mutt',
NAVER_MAILAPP: 'NaverMailApp',
NEWTON: 'Newton',
NINE: 'Nine',
NYLAS_MAIL: 'NylasMail',
OUTLOOK_EXPRESS: 'Outlook-Express',
PEGASUS_MAIL: 'Pegasus Mail',
POCOMAIL: 'PocoMail',
POLYMAIL: 'Polymail',
POSTBOX: 'Postbox',
PROTON_MAIL: 'ProtonMail',
PROTON_MAIL_BRIDGE: 'ProtonMail Bridge',
QUALA_MAIL: 'Quala',
R2MAIL2: 'R2Mail2',
RAINLOOP: 'RainLoop',
ROUNDCUBE: 'Roundcube Webmail',
SAMSUNG_EMAIL: 'SamsungEmail',
SPARK_MAIL: 'SparkDesktop',
SPARROW: 'Sparrow',
SPICEBIRD: 'Spicebird',
SQUIRRELMAIL: 'SquirrelMail',
SYLPHEED: 'Sylpheed',
THE_BAT: 'The Bat!',
THUNDERBIRD: 'Thunderbird',
YAHOO_MAIL: 'Yahoo',
TROJITA: 'Trojita',
TURNPIKE: 'Turnpike',
TUTANOTA: 'tutanota-desktop',
WANDERLUST: 'Wanderlust',
WINDOWS_LIVE_MAIL: 'Windows-Live-Mail',
YAHOO_MAIL: 'Yahoo Mail',
YAHOO_MAIL_IOS: 'Yahoo Mail',
ZIMBRA: 'Zimbra',
ZOHO_MAIL: 'ZohoMail-Desktop'
},
Fetcher: {
AHREFS_SITEAUDIT: 'AhrefsSiteAudit',
AMAZON_NOVA_ACT: 'NovaAct',
ANTHROPIC_CLAUDE_USER: 'Claude-User',
ASANA: 'Asana',
BETTER_UPTIME_BOT: 'Better Uptime Bot',
@@ -627,6 +686,7 @@ const Extension = Object.freeze({
BLUESKY: 'Bluesky',
BUFFER_LINKPREVIEWBOT: 'BufferLinkPreviewBot',
COHERE_AI: 'Cohere-AI',
DISCORD_BOT: 'Discordbot',
DUCKDUCKGO_ASSISTBOT: 'DuckAssistBot',
GOOGLE_CHROME_LIGHTHOUSE: 'Chrome-Lighthouse',
GOOGLE_FEEDFETCHER: 'FeedFetcher-Google',
@@ -639,6 +699,7 @@ const Extension = Object.freeze({
HUBSPOT_PAGE_FETCHER: 'HubSpot Page Fetcher',
IFRAMELY: 'Iframely',
KAKAOTALK_SCRAP: 'kakaotalk-scrap',
KEYBASE_BOT: 'KeybaseBot',
META_EXTERNALFETCHER: 'meta-externalfetcher',
META_WHATSAPP: 'WhatsApp',
MICROSOFT_BINGPREVIEW: 'BingPreview',
@@ -650,6 +711,9 @@ const Extension = Object.freeze({
PERPLEXITY_USER: 'Perplexity-User',
PINTEREST_BOT: 'Pinterestbot',
SEMRUSH_SITEAUDITBOT: 'SiteAuditBot',
SLACK_BOT: 'Slackbot',
SLACK_BOT_LINKEXPANDING: 'Slackbot-LinkExpanding',
SLACK_IMGPROXY: 'Slack-ImgProxy',
SNAP_URL_PREVIEW: 'Snap URL Preview',
SKYPE_URIPREVIEW: 'SkypeUriPreview',
TELEGRAM_BOT: 'TelegramBot',
@@ -659,6 +723,7 @@ const Extension = Object.freeze({
VERCEL_BOT: 'Vercelbot',
VERCEL_FLAGS: 'vercelflags',
VERCEL_TRACING: 'verceltracing',
X_TWITTERBOT: 'Twitterbot',
YANDEX_CALENDAR: 'YandexCalendar',
YANDEX_DIRECT: 'YandexDirect',
YANDEX_DIRECTDYN: 'YandexDirectDyn',
@@ -691,26 +756,36 @@ 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',
HTTP_RB: 'http.rb',
JAVA: 'Java',
JAVA_HTTPCLIENT: 'Java-http-client',
JETTY: 'Jetty',
JSDOM: 'jsdom',
LIBWWW_PERL: 'libwww-perl',
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',
OCAML_COHTTP: 'ocaml-cohttp',
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

@@ -3,7 +3,7 @@
// Source: /src/enums/ua-parser-enums.js
///////////////////////////////////////////////
/* Enums for UAParser.js v2.0.5
/* Enums for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -19,11 +19,13 @@ const BrowserName = Object.freeze({
AMAYA: 'Amaya',
ANDROID: 'Android Browser',
ARORA: 'Arora',
ATLAS: 'Atlas',
AVANT: 'Avant',
AVAST: 'Avast Secure Browser',
AVG: 'AVG Secure Browser',
BAIDU: 'Baidu Browser',
BASILISK: 'Basilisk',
BING: 'Bing',
BLAZER: 'Blazer',
BOLT: 'Bolt',
BOWSER: 'Bowser',
@@ -64,6 +66,7 @@ const BrowserName = Object.freeze({
GOOGLE_SEARCH: 'GSA',
HELIO: 'Helio',
HEYTAP: 'HeyTap',
HIBROWSER: 'HiBrowser',
HONOR: 'Honor',
HUAWEI: 'Huawei Browser',
ICAB: 'iCab',
@@ -114,6 +117,7 @@ const BrowserName = Object.freeze({
OPERA_GX: 'Opera GX',
OPERA_MINI: 'Opera Mini',
OPERA_MOBI: 'Opera Mobi',
OPERA_NEON: 'Opera Neon',
OPERA_TABLET: 'Opera Tablet',
OPERA_TOUCH: 'Opera Touch',
OTTER: 'Otter',
@@ -129,6 +133,7 @@ const BrowserName = Object.freeze({
QUARK: 'Quark',
QUPZILLA: 'QupZilla',
QUTEBROWSER: 'qutebrowser',
QWANT: 'Qwant',
REKONQ: 'rekonq',
ROCKMELT: 'Rockmelt',
SAFARI: 'Safari',
@@ -145,6 +150,7 @@ const BrowserName = Object.freeze({
SNAPCHAT: 'Snapchat',
SOGOU_EXPLORER: 'Sogou Explorer',
SOGOU_MOBILE: 'Sogou Mobile',
STEAM: 'Steam',
SURF: 'Surf',
SWIFTFOX: 'Swiftfox',
TESLA: 'Tesla',
@@ -226,8 +232,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',
@@ -243,6 +250,7 @@ const DeviceVendor = Object.freeze({
GEEKSPHONE: 'GeeksPhone',
GENERIC: 'Generic',
GOOGLE: 'Google',
HISENSE: 'Hisense',
HMD: 'HMD',
HP: 'HP',
HTC: 'HTC',
@@ -255,6 +263,7 @@ const DeviceVendor = Object.freeze({
LAVA: 'Lava',
LENOVO: 'Lenovo',
LG: 'LG',
LOGITECH: 'Logitech',
MEIZU: 'Meizu',
MICROMAX: 'Micromax',
MICROSOFT: 'Microsoft',
@@ -288,9 +297,11 @@ const DeviceVendor = Object.freeze({
TECNO: 'TECNO',
TESLA: 'Tesla',
ULEFONE: 'Ulefone',
VALVE: 'Valve',
VIVO: 'Vivo',
VIZIO: 'Vizio',
VODAFONE: 'Vodafone',
WIKO: 'Wiko',
XBOX: 'Xbox',
XIAOMI: 'Xiaomi',
ZEBRA: 'Zebra',
@@ -307,6 +318,7 @@ const EngineName = Object.freeze({
AMAYA: 'Amaya',
ARKWEB: 'ArkWeb',
BLINK: 'Blink',
DILLO: 'Dillo',
EDGEHTML: 'EdgeHTML',
FLOW: 'Flow',
GECKO: 'Gecko',
@@ -442,6 +454,7 @@ const Extension = Object.freeze({
ELINKS: 'ELinks',
HTTPIE: 'HTTPie',
LYNX: 'Lynx',
POWERSHELL: 'PowerShell',
WGET: 'Wget'
},
Crawler: {
@@ -489,6 +502,7 @@ const Extension = Object.freeze({
DUCKDUCKGO_BOT: 'DuckDuckBot',
DUCKDUCKGO_FAVICONS_BOT: 'DuckDuckGo-Favicons-Bot',
ELASTIC: 'Elastic',
ELASTIC_SWIFTYPE_BOT: 'Swiftbot',
EXALEAD_EXABOT: 'Exabot',
FIRECRAWL_AGENT: 'FirecrawlAgent',
FREESPOKE: 'Freespoke',
@@ -538,6 +552,7 @@ const Extension = Object.freeze({
PERPLEXITY_BOT: 'PerplexityBot',
QIHOO_360_SPIDER: '360Spider',
QWANT_BOT: 'Qwantbot',
QWANT_BOT_NEWS: 'Qwantbot-news',
REPLICATE_BOT: 'Replicate-Bot',
RUNPOD_BOT: 'RunPod-Bot',
SB_INTUITIONS_BOT: 'SBIntuitionsBot',
@@ -551,6 +566,7 @@ const Extension = Object.freeze({
SOGOU_PIC_SPIDER: 'Sogou Pic Spider',
SOGOU_WEB_SPIDER: 'Sogou web spider',
STARTPAGE: 'Startpage',
SURLY_BOT: 'SurdotlyBot',
TIMPI_BOT: 'Timpibot',
TOGETHER_BOT: 'Together-Bot',
TURNITIN_BOT: 'TurnitinBot',
@@ -601,29 +617,72 @@ const Extension = Object.freeze({
},
Email: {
AIRMAIL: 'Airmail',
ALPINE: 'Alpine',
ANDROID_MAIL: 'Android',
APPLE_MAIL: 'Mail',
AQUA_MAIL: 'AquaMail',
BALSA: 'Balsa',
BARCA: 'Barca',
BLUEMAIL: 'BlueMail',
CANARY: 'Canary',
CLAWS_MAIL: 'Claws Mail',
DAUM_MAIL: 'DaumMail',
EVOLUTION: 'Evolution',
EM_CLIENT: 'eM Client',
EUDORA: 'Eudora',
EVOLUTION: 'Evolution',
FAIR_EMAIL: 'FairEmail',
FOXMAIL: 'Foxmail',
GEARY: 'Geary',
GNUS: 'Gnus',
HORDE_IMP: 'Horde::IMP',
IBM_NOTES: 'Lotus-Notes',
INCREDIMAIL: 'IncrediMail',
K9_MAIL: 'K-9 Mail',
KMAIL: 'KMail',
KMAIL2: 'kmail2',
KONTACT: 'Kontact',
MAILBIRD: 'Mailbird',
MAILMATE: 'MailMate',
MAILSPRING: 'Mailspring',
MICROSOFT_OUTLOOK: 'Microsoft Outlook',
MICROSOFT_OUTLOOK_MAC: 'MacOutlook',
MUTT: 'Mutt',
NAVER_MAILAPP: 'NaverMailApp',
NEWTON: 'Newton',
NINE: 'Nine',
NYLAS_MAIL: 'NylasMail',
OUTLOOK_EXPRESS: 'Outlook-Express',
PEGASUS_MAIL: 'Pegasus Mail',
POCOMAIL: 'PocoMail',
POLYMAIL: 'Polymail',
POSTBOX: 'Postbox',
PROTON_MAIL: 'ProtonMail',
PROTON_MAIL_BRIDGE: 'ProtonMail Bridge',
QUALA_MAIL: 'Quala',
R2MAIL2: 'R2Mail2',
RAINLOOP: 'RainLoop',
ROUNDCUBE: 'Roundcube Webmail',
SAMSUNG_EMAIL: 'SamsungEmail',
SPARK_MAIL: 'SparkDesktop',
SPARROW: 'Sparrow',
SPICEBIRD: 'Spicebird',
SQUIRRELMAIL: 'SquirrelMail',
SYLPHEED: 'Sylpheed',
THE_BAT: 'The Bat!',
THUNDERBIRD: 'Thunderbird',
YAHOO_MAIL: 'Yahoo',
TROJITA: 'Trojita',
TURNPIKE: 'Turnpike',
TUTANOTA: 'tutanota-desktop',
WANDERLUST: 'Wanderlust',
WINDOWS_LIVE_MAIL: 'Windows-Live-Mail',
YAHOO_MAIL: 'Yahoo Mail',
YAHOO_MAIL_IOS: 'Yahoo Mail',
ZIMBRA: 'Zimbra',
ZOHO_MAIL: 'ZohoMail-Desktop'
},
Fetcher: {
AHREFS_SITEAUDIT: 'AhrefsSiteAudit',
AMAZON_NOVA_ACT: 'NovaAct',
ANTHROPIC_CLAUDE_USER: 'Claude-User',
ASANA: 'Asana',
BETTER_UPTIME_BOT: 'Better Uptime Bot',
@@ -631,6 +690,7 @@ const Extension = Object.freeze({
BLUESKY: 'Bluesky',
BUFFER_LINKPREVIEWBOT: 'BufferLinkPreviewBot',
COHERE_AI: 'Cohere-AI',
DISCORD_BOT: 'Discordbot',
DUCKDUCKGO_ASSISTBOT: 'DuckAssistBot',
GOOGLE_CHROME_LIGHTHOUSE: 'Chrome-Lighthouse',
GOOGLE_FEEDFETCHER: 'FeedFetcher-Google',
@@ -643,6 +703,7 @@ const Extension = Object.freeze({
HUBSPOT_PAGE_FETCHER: 'HubSpot Page Fetcher',
IFRAMELY: 'Iframely',
KAKAOTALK_SCRAP: 'kakaotalk-scrap',
KEYBASE_BOT: 'KeybaseBot',
META_EXTERNALFETCHER: 'meta-externalfetcher',
META_WHATSAPP: 'WhatsApp',
MICROSOFT_BINGPREVIEW: 'BingPreview',
@@ -654,6 +715,9 @@ const Extension = Object.freeze({
PERPLEXITY_USER: 'Perplexity-User',
PINTEREST_BOT: 'Pinterestbot',
SEMRUSH_SITEAUDITBOT: 'SiteAuditBot',
SLACK_BOT: 'Slackbot',
SLACK_BOT_LINKEXPANDING: 'Slackbot-LinkExpanding',
SLACK_IMGPROXY: 'Slack-ImgProxy',
SNAP_URL_PREVIEW: 'Snap URL Preview',
SKYPE_URIPREVIEW: 'SkypeUriPreview',
TELEGRAM_BOT: 'TelegramBot',
@@ -663,6 +727,7 @@ const Extension = Object.freeze({
VERCEL_BOT: 'Vercelbot',
VERCEL_FLAGS: 'vercelflags',
VERCEL_TRACING: 'verceltracing',
X_TWITTERBOT: 'Twitterbot',
YANDEX_CALENDAR: 'YandexCalendar',
YANDEX_DIRECT: 'YandexDirect',
YANDEX_DIRECTDYN: 'YandexDirectDyn',
@@ -695,26 +760,36 @@ 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',
HTTP_RB: 'http.rb',
JAVA: 'Java',
JAVA_HTTPCLIENT: 'Java-http-client',
JETTY: 'Jetty',
JSDOM: 'jsdom',
LIBWWW_PERL: 'libwww-perl',
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',
OCAML_COHTTP: 'ocaml-cohttp',
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,4 +1,4 @@
// Type definitions for Helpers submodule of UAParser.js v2.0.5
// Type definitions for Helpers submodule of UAParser.js v2.0.8
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////
/* Extensions for UAParser.js v2.0.5
/* Extensions for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -22,14 +22,27 @@ const INAPP = 'inapp';
const MEDIAPLAYER = 'mediaplayer';
const LIBRARY = 'library';
// Helper to normalize specific email client names
const normalizeEmailName = function (str) {
const map = {
'YahooMobile': 'Yahoo Mail',
'YahooMail': 'Yahoo Mail',
'K-9': 'K-9 Mail',
'K-9 Mail': 'K-9 Mail',
'Zdesktop': 'Zimbra',
'zdesktop': 'Zimbra'
};
return map[str] || str;
};
//////////////////////
// COMMAND LINE APPS
/////////////////////
const CLIs = Object.freeze({
browser : [
// wget / curl / Lynx / ELinks / HTTPie
[/(wget|curl|lynx|elinks|httpie)[\/ ]\(?([\w\.-]+)/i], [NAME, VERSION, [TYPE, CLI]]
// wget / curl / Lynx / ELinks / HTTPie / PowerShell
[/(wget|curl|lynx|elinks|httpie|powershell)[\/ ]\(?([\w\.-]+)/i], [NAME, VERSION, [TYPE, CLI]]
]
});
@@ -63,8 +76,10 @@ const Crawlers = Object.freeze({
// PerplexityBot - https://perplexity.ai/perplexitybot
// SBIntuitionsBot - https://www.sbintuitions.co.jp/bot/
// SeznamBot - http://napoveda.seznam.cz/seznambot-intro
// SurdotlyBot - http://sur.ly/bot.html
// Swiftbot - https://swiftype.com/swiftbot
// YepBot - https://yep.com/yepbot/
/((?:adidx|ahrefs|amazon|bing|brave|cc|contx|coveo|criteo|dot|duckduck(?:go-favicons-)?|exa|facebook|gpt|iask|kagi|kangaroo |linkedin|mj12|mojeek|oai-search|onespot-scraper|perplexity|sbintuitions|semrush|seznam|yep)bot)\/([\w\.-]+)/i,
/((?:adidx|ahrefs|amazon|bing|brave|cc|contx|coveo|criteo|dot|duckduck(?:go-favicons-)?|exa|facebook|gpt|iask|kagi|kangaroo |linkedin|mj12|mojeek|oai-search|onespot-scraper|perplexity|sbintuitions|semrush|seznam|surdotly|swift|yep)bot)\/([\w\.-]+)/i,
// Algolia Crawler
/(algolia crawler(?: renderscript)?)\/?([\w\.]*)/i,
@@ -98,7 +113,7 @@ const Crawlers = Object.freeze({
/(oncrawl) mobile\/([\w\.]+)/i,
// Qwantbot - https://help.qwant.com/bot
/(qwantbot)[-\w]*\/?([\w\.]*)/i,
/(qwantbot(?:-news)?)[-\w]*\/?([\w\.]*)/i,
// SemrushBot - http://www.semrush.com/bot.html
/((?:semrush|splitsignal)bot[-abcfimostw]*)\/?([\w\.-]*)/i,
@@ -227,25 +242,55 @@ const ExtraDevices = Object.freeze({
]
});
///////////////
//////////////
// EMAIL APPS
//////////////
const Emails = Object.freeze({
browser : [
// 1. Specific Android Mail Rule
[/(android)\/([\w\.-]+email)/i],
[NAME, VERSION, [TYPE, EMAIL]],
// 2. Standard Email Clients
[
// Evolution / Kontact/KMail[2] / [Microsoft/Mac] Outlook / Thunderbird
// Airmail / BlueMail / DaumMail / eMClient / Foxmail / NaverMailApp / Polymail
// ProtonMail / SparkDesktop / Sparrow / Yahoo! Mail / Zimbra / ZohoMail-Desktop
/((?:air|blue|daum|fox|poly|proton)mail|emclient|evolution|kmail2?|kontact|(?:microsoft |mac)?outlook(?:-express)?|navermailapp|(?!chrom.+)sparrow|sparkdesktop|thunderbird|yahoo|zohomail-desktop)(?:m.+ail; |[\/ ])([\w\.]+)/i,
new RegExp(
'(' +
// Clients ending in 'mail' (Case 1: Prefix + optional space + [e]mail)
// Covers: AirMail, Claws Mail, FairEmail, SamsungEmail, Yahoo Mail, etc.
'(?:air|aqua|blue|claws|daum|fair|fox|k-9|mac|nylas|pegasus|poco|poly|proton|samsung|squirrel|yahoo) ?e?mail(?:-desktop| app| bridge)?|' +
// Standalone / Specific Names
'microsoft outlook|r2mail2|spicebird|turnpike|yahoomobile|' +
// Microsoft & Outlook Variants
'(?:microsoft )?outlook(?:-express)?|macoutlook|windows-live-mail|' +
// Specific Clients
'alpine|balsa|barca|canary|emclient|eudora|evolution|geary|gnus|' +
'horde::imp|incredimail|kmail2?|kontact|lotus-notes|' +
'mail(?:bird|mate|spring)|mutt|navermailapp|newton|nine|postbox|' +
'rainloop|roundcube webmail|spar(?:row|kdesktop)|sylpheed|' +
'the bat!|thunderbird|trojita|tutanota-desktop|wanderlust|' +
'zdesktop|zohomail-desktop' +
')' +
// Separator
'(?:m.+ail; |[\\/ ])' +
// Version (Updated to allow hyphens for Turnpike)
'([\\w\\.-]+)',
'i'
)
],
[
[NAME, normalizeEmailName],
VERSION,
[TYPE, EMAIL]
],
// Apple's Mail
/(mail)\/([\w\.]+) cf/i
], [NAME, VERSION, [TYPE, EMAIL]], [
// 3. Apple Mail Context
[/(mail)\/([\w\.]+) cf/i],
[NAME, VERSION, [TYPE, EMAIL]],
// Zimbra
/zdesktop\/([\w\.]+)/i
], [VERSION, [NAME, 'Zimbra'], [TYPE, EMAIL]]
// 4. Zimbra Server
[/(zimbra)\/([\w\.-]+)/i],
[NAME, VERSION, [TYPE, EMAIL]]
]
});
@@ -256,7 +301,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
@@ -266,25 +311,25 @@ 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]],
[
// Google Bots / Chrome-Lighthouse / Gemini-Deep-Research / Snapchat / Vercelbot / Yandex Bots
/((?:better uptime |telegram|vercel)bot|chrome-lighthouse|feedfetcher-google|gemini-deep-research|google(?:imageproxy|-read-aloud|-pagerenderer|producer)|snap url preview|vercel(flags|tracing|-(favicon|screenshot)-bot)|yandex(?:sitelinks|userproxy))/i
// Google Bots / Chrome-Lighthouse / Gemini-Deep-Research / KeybaseBot / Snapchat / Vercelbot / Yandex Bots
/((?:better uptime |keybase|telegram|vercel)bot|chrome-lighthouse|feedfetcher-google|gemini-deep-research|google(?:imageproxy|-read-aloud|-pagerenderer|producer)|snap url preview|vercel(flags|tracing|-(favicon|screenshot)-bot)|yandex(?:sitelinks|userproxy))/i
],
[NAME, [TYPE, FETCHER]],
],
@@ -387,13 +432,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/http.rb/Java[-HttpClient]/Jetty/jsdom/libwww-perl/lua-resty-http/Needle/Node.js/node-fetch/ocaml-cohttp/OkHttp/PHP-SOAP/PostmanRuntime/python-urllib/python-requests/rest-client/Scrapy/superagent
/^((?:apache|go|java)-http-?client|axios|bun|dart|deno|got|(?:guzzle|lua-resty-|ocaml-co|ok)http|hackney|http\.rb|java|jetty|libwww-perl|needle|node(?:\.js|-fetch|-superagent)|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

@@ -3,7 +3,7 @@
// Source: /src/extensions/ua-parser-extensions.js
///////////////////////////////////////////////
/* Extensions for UAParser.js v2.0.5
/* Extensions for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -26,14 +26,27 @@ const INAPP = 'inapp';
const MEDIAPLAYER = 'mediaplayer';
const LIBRARY = 'library';
// Helper to normalize specific email client names
const normalizeEmailName = function (str) {
const map = {
'YahooMobile': 'Yahoo Mail',
'YahooMail': 'Yahoo Mail',
'K-9': 'K-9 Mail',
'K-9 Mail': 'K-9 Mail',
'Zdesktop': 'Zimbra',
'zdesktop': 'Zimbra'
};
return map[str] || str;
};
//////////////////////
// COMMAND LINE APPS
/////////////////////
const CLIs = Object.freeze({
browser : [
// wget / curl / Lynx / ELinks / HTTPie
[/(wget|curl|lynx|elinks|httpie)[\/ ]\(?([\w\.-]+)/i], [NAME, VERSION, [TYPE, CLI]]
// wget / curl / Lynx / ELinks / HTTPie / PowerShell
[/(wget|curl|lynx|elinks|httpie|powershell)[\/ ]\(?([\w\.-]+)/i], [NAME, VERSION, [TYPE, CLI]]
]
});
@@ -67,8 +80,10 @@ const Crawlers = Object.freeze({
// PerplexityBot - https://perplexity.ai/perplexitybot
// SBIntuitionsBot - https://www.sbintuitions.co.jp/bot/
// SeznamBot - http://napoveda.seznam.cz/seznambot-intro
// SurdotlyBot - http://sur.ly/bot.html
// Swiftbot - https://swiftype.com/swiftbot
// YepBot - https://yep.com/yepbot/
/((?:adidx|ahrefs|amazon|bing|brave|cc|contx|coveo|criteo|dot|duckduck(?:go-favicons-)?|exa|facebook|gpt|iask|kagi|kangaroo |linkedin|mj12|mojeek|oai-search|onespot-scraper|perplexity|sbintuitions|semrush|seznam|yep)bot)\/([\w\.-]+)/i,
/((?:adidx|ahrefs|amazon|bing|brave|cc|contx|coveo|criteo|dot|duckduck(?:go-favicons-)?|exa|facebook|gpt|iask|kagi|kangaroo |linkedin|mj12|mojeek|oai-search|onespot-scraper|perplexity|sbintuitions|semrush|seznam|surdotly|swift|yep)bot)\/([\w\.-]+)/i,
// Algolia Crawler
/(algolia crawler(?: renderscript)?)\/?([\w\.]*)/i,
@@ -102,7 +117,7 @@ const Crawlers = Object.freeze({
/(oncrawl) mobile\/([\w\.]+)/i,
// Qwantbot - https://help.qwant.com/bot
/(qwantbot)[-\w]*\/?([\w\.]*)/i,
/(qwantbot(?:-news)?)[-\w]*\/?([\w\.]*)/i,
// SemrushBot - http://www.semrush.com/bot.html
/((?:semrush|splitsignal)bot[-abcfimostw]*)\/?([\w\.-]*)/i,
@@ -231,25 +246,55 @@ const ExtraDevices = Object.freeze({
]
});
///////////////
//////////////
// EMAIL APPS
//////////////
const Emails = Object.freeze({
browser : [
// 1. Specific Android Mail Rule
[/(android)\/([\w\.-]+email)/i],
[NAME, VERSION, [TYPE, EMAIL]],
// 2. Standard Email Clients
[
// Evolution / Kontact/KMail[2] / [Microsoft/Mac] Outlook / Thunderbird
// Airmail / BlueMail / DaumMail / eMClient / Foxmail / NaverMailApp / Polymail
// ProtonMail / SparkDesktop / Sparrow / Yahoo! Mail / Zimbra / ZohoMail-Desktop
/((?:air|blue|daum|fox|poly|proton)mail|emclient|evolution|kmail2?|kontact|(?:microsoft |mac)?outlook(?:-express)?|navermailapp|(?!chrom.+)sparrow|sparkdesktop|thunderbird|yahoo|zohomail-desktop)(?:m.+ail; |[\/ ])([\w\.]+)/i,
new RegExp(
'(' +
// Clients ending in 'mail' (Case 1: Prefix + optional space + [e]mail)
// Covers: AirMail, Claws Mail, FairEmail, SamsungEmail, Yahoo Mail, etc.
'(?:air|aqua|blue|claws|daum|fair|fox|k-9|mac|nylas|pegasus|poco|poly|proton|samsung|squirrel|yahoo) ?e?mail(?:-desktop| app| bridge)?|' +
// Standalone / Specific Names
'microsoft outlook|r2mail2|spicebird|turnpike|yahoomobile|' +
// Microsoft & Outlook Variants
'(?:microsoft )?outlook(?:-express)?|macoutlook|windows-live-mail|' +
// Specific Clients
'alpine|balsa|barca|canary|emclient|eudora|evolution|geary|gnus|' +
'horde::imp|incredimail|kmail2?|kontact|lotus-notes|' +
'mail(?:bird|mate|spring)|mutt|navermailapp|newton|nine|postbox|' +
'rainloop|roundcube webmail|spar(?:row|kdesktop)|sylpheed|' +
'the bat!|thunderbird|trojita|tutanota-desktop|wanderlust|' +
'zdesktop|zohomail-desktop' +
')' +
// Separator
'(?:m.+ail; |[\\/ ])' +
// Version (Updated to allow hyphens for Turnpike)
'([\\w\\.-]+)',
'i'
)
],
[
[NAME, normalizeEmailName],
VERSION,
[TYPE, EMAIL]
],
// Apple's Mail
/(mail)\/([\w\.]+) cf/i
], [NAME, VERSION, [TYPE, EMAIL]], [
// 3. Apple Mail Context
[/(mail)\/([\w\.]+) cf/i],
[NAME, VERSION, [TYPE, EMAIL]],
// Zimbra
/zdesktop\/([\w\.]+)/i
], [VERSION, [NAME, 'Zimbra'], [TYPE, EMAIL]]
// 4. Zimbra Server
[/(zimbra)\/([\w\.-]+)/i],
[NAME, VERSION, [TYPE, EMAIL]]
]
});
@@ -260,7 +305,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
@@ -270,25 +315,25 @@ 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]],
[
// Google Bots / Chrome-Lighthouse / Gemini-Deep-Research / Snapchat / Vercelbot / Yandex Bots
/((?:better uptime |telegram|vercel)bot|chrome-lighthouse|feedfetcher-google|gemini-deep-research|google(?:imageproxy|-read-aloud|-pagerenderer|producer)|snap url preview|vercel(flags|tracing|-(favicon|screenshot)-bot)|yandex(?:sitelinks|userproxy))/i
// Google Bots / Chrome-Lighthouse / Gemini-Deep-Research / KeybaseBot / Snapchat / Vercelbot / Yandex Bots
/((?:better uptime |keybase|telegram|vercel)bot|chrome-lighthouse|feedfetcher-google|gemini-deep-research|google(?:imageproxy|-read-aloud|-pagerenderer|producer)|snap url preview|vercel(flags|tracing|-(favicon|screenshot)-bot)|yandex(?:sitelinks|userproxy))/i
],
[NAME, [TYPE, FETCHER]],
],
@@ -391,13 +436,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/http.rb/Java[-HttpClient]/Jetty/jsdom/libwww-perl/lua-resty-http/Needle/Node.js/node-fetch/ocaml-cohttp/OkHttp/PHP-SOAP/PostmanRuntime/python-urllib/python-requests/rest-client/Scrapy/superagent
/^((?:apache|go|java)-http-?client|axios|bun|dart|deno|got|(?:guzzle|lua-resty-|ocaml-co|ok)http|hackney|http\.rb|java|jetty|libwww-perl|needle|node(?:\.js|-fetch|-superagent)|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,15 +1,41 @@
// Type definitions for Helpers submodule of UAParser.js v2.0.5
// Type definitions for Helpers submodule of UAParser.js v2.0.8
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
import type { IResult } from "../main/ua-parser";
export function getDeviceVendor(model: string): string | undefined;
export function isAppleSilicon(resultOrUA: IResult | string): boolean;
export function isAIBot(resultOrUA: IResult | string): boolean;
export function isBot(resultOrUA: IResult | string): boolean;
export function isChromeFamily(resultOrUA: IResult | string): boolean;
export function isElectron(): boolean;
export function isFromEU(): boolean;
export function getOutlookEdition(name: string, version: string): string;
export function isFrozenUA(ua: string): boolean;
/**
* @deprecated Moved to `device-detection` submodule
*/
export function getDeviceVendor(model: string): string | undefined;
/**
* @deprecated Moved to `device-detection` submodule
*/
export function isAppleSilicon(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `bot-detection` submodule
*/
export function isAIBot(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `bot-detection` submodule
*/
export function isBot(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isChromeFamily(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isElectron(): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isFromEU(): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isStandalonePWA(): boolean;

View File

@@ -1,5 +1,5 @@
///////////////////////////////////////////////
/* Helpers for UAParser.js v2.0.5
/* Helpers for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -7,170 +7,95 @@
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { CPUArch, OSName, EngineName, Extension, BrowserType } = require('../enums/ua-parser-enums');
const { Bots, Crawlers } = require('../extensions/ua-parser-extensions');
const { isFromEU } = require('detect-europe-js');
const { getDeviceVendor: _getDeviceVendor, isAppleSilicon: _isAppleSilicon } = require('../device-detection/device-detection');
const { isBot: _isBot, isAICrawler } = require('../bot-detection/bot-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');
const { isStandalonePWA } = require('is-standalone-pwa');
const { Crawler } = Extension.BrowserName;
const toResult = (value, head, ext) => typeof value === 'string' ? UAParser(value, head, ext) : value;
/**
* @deprecated Moved to `device-detection` submodule
*/
const getDeviceVendor = _getDeviceVendor;
const getDeviceVendor = (model) => UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).device.vendor;
/**
* @deprecated Moved to `device-detection` submodule
*/
const isAppleSilicon = _isAppleSilicon;
const isAppleSilicon = (resultOrUA) => {
const res = toResult(resultOrUA);
if (res.os.is(OSName.MACOS)) {
if (res.cpu.is(CPUArch.ARM)) {
return true;
}
if (typeof resultOrUA !== 'string' && typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const debug = webgl.getExtension('WEBGL_debug_renderer_info');
const renderer = webgl.getParameter(debug.UNMASKED_RENDERER_WEBGL);
if (renderer.match(/apple m\d/i)) {
return true;
}
} catch {
return false;
/**
* @deprecated Moved to `bot-detection` submodule
*/
const isAIBot = isAICrawler;
/**
* @deprecated Moved to `bot-detection` submodule
*/
const isBot = _isBot;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isChromeFamily = _isChromeFamily;
/**
* @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;
/**
* Translates a raw Outlook User-Agent name/version into a
* Developer-friendly Edition (e.g., "Outlook 2019 (Modern Word)").
*/
const getOutlookEdition = (name, version) => {
if (!name || !version) return name;
const cleanName = name.toLowerCase().replace(/microsoft\s+/, '');
// 1. Handle Mac Separately (Different Rendering Engine)
if (cleanName === 'macoutlook') {
const major = parseInt(version.split('.')[0], 10);
if (major >= 16) return "Outlook for Mac (Modern)";
return "Outlook for Mac (Legacy)";
}
// 2. Handle Windows Outlook
if (cleanName === 'outlook') {
const parts = version.split('.').map(Number);
const major = parts[0];
const build = parts[2] || 0; // Build number is usually the 3rd part
// Pre-2016 Versions (Clear Major Version mapping)
if (major === 15) return "Outlook 2013";
if (major === 14) return "Outlook 2010";
if (major === 12) return "Outlook 2007";
if (major < 12) return "Outlook (Legacy)";
// The Version 16.0 Confusion
if (major === 16) {
// Build < 10000 = MSI (Volume License 2016/2019)
// These render poorly (No SVG, older bugs)
if (build < 10000) {
return "Outlook 2016 (MSI / Volume License)";
}
// Build >= 10000 = Click-to-Run (Retail 2016 / 2019 / 365)
// These render well (SVG support, modern CSS)
return "Outlook 365 / 2019+ (Modern)";
}
}
return false;
}
const isAIBot = (resultOrUA) => [
// AI2
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]
.map((s) => s.toLowerCase())
.includes(String(toResult(resultOrUA, Crawlers).browser.name).toLowerCase());
const isBot = (resultOrUA) => [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
].includes(toResult(resultOrUA, Bots).browser.type);
const isChromeFamily = (resultOrUA) => toResult(resultOrUA).engine.is(EngineName.BLINK);
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
// 3. Fallback for 'Outlook Express' or 'New Outlook' (Browser)
return name;
};
module.exports = {
getDeviceVendor,
@@ -181,5 +106,6 @@ module.exports = {
isElectron,
isFromEU,
isFrozenUA,
isStandalonePWA
isStandalonePWA,
getOutlookEdition
}

View File

@@ -3,7 +3,7 @@
// Source: /src/helpers/ua-parser-helpers.js
///////////////////////////////////////////////
/* Helpers for UAParser.js v2.0.5
/* Helpers for UAParser.js v2.0.8
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
@@ -11,170 +11,95 @@
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { CPUArch, OSName, EngineName, Extension, BrowserType } from '../enums/ua-parser-enums.mjs';
import { Bots, Crawlers } from '../extensions/ua-parser-extensions.mjs';
import { isFromEU } from 'detect-europe-js';
import { getDeviceVendor as _getDeviceVendor, isAppleSilicon as _isAppleSilicon } from '../device-detection/device-detection.mjs';
import { isBot as _isBot, isAICrawler } from '../bot-detection/bot-detection.mjs';
import { isChromeFamily as _isChromeFamily, isElectron as _isElectron, isStandalonePWA as _isStandalonePWA } from '../browser-detection/browser-detection.mjs';
import { isFromEU as _isFromEU } from '../browser-detection/browser-detection.mjs';
import { isFrozenUA } from 'ua-is-frozen';
import { isStandalonePWA } from 'is-standalone-pwa';
const { Crawler } = Extension.BrowserName;
const toResult = (value, head, ext) => typeof value === 'string' ? UAParser(value, head, ext) : value;
/**
* @deprecated Moved to `device-detection` submodule
*/
const getDeviceVendor = _getDeviceVendor;
const getDeviceVendor = (model) => UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).device.vendor;
/**
* @deprecated Moved to `device-detection` submodule
*/
const isAppleSilicon = _isAppleSilicon;
const isAppleSilicon = (resultOrUA) => {
const res = toResult(resultOrUA);
if (res.os.is(OSName.MACOS)) {
if (res.cpu.is(CPUArch.ARM)) {
return true;
}
if (typeof resultOrUA !== 'string' && typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const debug = webgl.getExtension('WEBGL_debug_renderer_info');
const renderer = webgl.getParameter(debug.UNMASKED_RENDERER_WEBGL);
if (renderer.match(/apple m\d/i)) {
return true;
}
} catch {
return false;
/**
* @deprecated Moved to `bot-detection` submodule
*/
const isAIBot = isAICrawler;
/**
* @deprecated Moved to `bot-detection` submodule
*/
const isBot = _isBot;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isChromeFamily = _isChromeFamily;
/**
* @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;
/**
* Translates a raw Outlook User-Agent name/version into a
* Developer-friendly Edition (e.g., "Outlook 2019 (Modern Word)").
*/
const getOutlookEdition = (name, version) => {
if (!name || !version) return name;
const cleanName = name.toLowerCase().replace(/microsoft\s+/, '');
// 1. Handle Mac Separately (Different Rendering Engine)
if (cleanName === 'macoutlook') {
const major = parseInt(version.split('.')[0], 10);
if (major >= 16) return "Outlook for Mac (Modern)";
return "Outlook for Mac (Legacy)";
}
// 2. Handle Windows Outlook
if (cleanName === 'outlook') {
const parts = version.split('.').map(Number);
const major = parts[0];
const build = parts[2] || 0; // Build number is usually the 3rd part
// Pre-2016 Versions (Clear Major Version mapping)
if (major === 15) return "Outlook 2013";
if (major === 14) return "Outlook 2010";
if (major === 12) return "Outlook 2007";
if (major < 12) return "Outlook (Legacy)";
// The Version 16.0 Confusion
if (major === 16) {
// Build < 10000 = MSI (Volume License 2016/2019)
// These render poorly (No SVG, older bugs)
if (build < 10000) {
return "Outlook 2016 (MSI / Volume License)";
}
// Build >= 10000 = Click-to-Run (Retail 2016 / 2019 / 365)
// These render well (SVG support, modern CSS)
return "Outlook 365 / 2019+ (Modern)";
}
}
return false;
}
const isAIBot = (resultOrUA) => [
// AI2
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]
.map((s) => s.toLowerCase())
.includes(String(toResult(resultOrUA, Crawlers).browser.name).toLowerCase());
const isBot = (resultOrUA) => [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
].includes(toResult(resultOrUA, Bots).browser.type);
const isChromeFamily = (resultOrUA) => toResult(resultOrUA).engine.is(EngineName.BLINK);
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
// 3. Fallback for 'Outlook Express' or 'New Outlook' (Browser)
return name;
};
export {
getDeviceVendor,
@@ -185,5 +110,6 @@ export {
isElectron,
isFromEU,
isFrozenUA,
isStandalonePWA
isStandalonePWA,
getOutlookEdition
}

View File

@@ -1,8 +1,7 @@
// Type definitions for UAParser.js v2.0.5
// Type definitions for UAParser.js v2.0.8
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
import type { Headers } from "undici";
import { BrowserType, CPUArch, DeviceType, EngineName } from "../enums/ua-parser-enums";
declare namespace UAParser {

View File

@@ -1,5 +1,5 @@
/////////////////////////////////////////////////////////////////////////////////
/* UAParser.js v2.0.5
/* UAParser.js v2.0.8
Copyright © 2012-2025 Faisal Salman <f@faisalman.com>
AGPLv3 License *//*
Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
@@ -19,25 +19,25 @@
// Constants
/////////////
var LIBVERSION = '2.0.5',
var LIBVERSION = '2.0.8',
UA_MAX_LENGTH = 500,
USER_AGENT = 'user-agent',
EMPTY = '',
UNKNOWN = '?',
// typeof
FUNC_TYPE = 'function',
UNDEF_TYPE = 'undefined',
OBJ_TYPE = 'object',
STR_TYPE = 'string',
TYPEOF = {
FUNCTION : 'function',
OBJECT : 'object',
STRING : 'string',
UNDEFINED : 'undefined'
},
// properties
UA_BROWSER = 'browser',
UA_CPU = 'cpu',
UA_DEVICE = 'device',
UA_ENGINE = 'engine',
UA_OS = 'os',
UA_RESULT = 'result',
BROWSER = 'browser',
CPU = 'cpu',
DEVICE = 'device',
ENGINE = 'engine',
OS = 'os',
RESULT = 'result',
NAME = 'name',
TYPE = 'type',
@@ -66,16 +66,16 @@
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',
CH_HEADER_BITNESS = CH_HEADER + '-' + BITNESS,
CH_HEADER_FORM_FACTORS = CH_HEADER + '-form-factors',
CH_HEADER_MOBILE = CH_HEADER + '-' + MOBILE,
CH_HEADER_MODEL = CH_HEADER + '-' + MODEL,
CH_HEADER_PLATFORM = CH_HEADER + '-' + PLATFORM,
CH_HEADER_PLATFORM_VER = CH_HEADER_PLATFORM + '-version',
CH_ALL_VALUES = [BRANDS, FULLVERLIST, MOBILE, MODEL, PLATFORM, PLATFORMVER, ARCHITECTURE, FORMFACTORS, BITNESS],
CH = 'sec-ch-ua',
CH_FULL_VER_LIST= CH + '-full-version-list',
CH_ARCH = CH + '-arch',
CH_BITNESS = CH + '-' + BITNESS,
CH_FORM_FACTORS = CH + '-form-factors',
CH_MOBILE = CH + '-' + MOBILE,
CH_MODEL = CH + '-' + MODEL,
CH_PLATFORM = CH + '-' + PLATFORM,
CH_PLATFORM_VER = CH_PLATFORM + '-version',
CH_ALL_VALUES = [BRANDS, FULLVERLIST, MOBILE, MODEL, PLATFORM, PLATFORMVER, ARCHITECTURE, FORMFACTORS, BITNESS],
// device vendors
AMAZON = 'Amazon',
@@ -114,7 +114,7 @@
// os
WINDOWS = 'Windows';
var isWindow = typeof window !== UNDEF_TYPE,
var isWindow = typeof window !== TYPEOF.UNDEFINED,
NAVIGATOR = (isWindow && window.navigator) ?
window.navigator :
undefined,
@@ -150,7 +150,7 @@
return enums;
},
has = function (str1, str2) {
if (typeof str1 === OBJ_TYPE && str1.length > 0) {
if (typeof str1 === TYPEOF.OBJECT && str1.length > 0) {
for (var i in str1) {
if (lowerize(str2) == lowerize(str1[i])) return true;
}
@@ -164,7 +164,7 @@
}
},
isString = function (val) {
return typeof val === STR_TYPE;
return typeof val === TYPEOF.STRING;
},
itemListToArray = function (header) {
if (!header) return undefined;
@@ -191,7 +191,7 @@
if (!arr.hasOwnProperty(i)) continue;
var propName = arr[i];
if (typeof propName == OBJ_TYPE && propName.length == 2) {
if (typeof propName == TYPEOF.OBJECT && propName.length == 2) {
this[propName[0]] = propName[1];
} else {
this[propName] = undefined;
@@ -206,10 +206,8 @@
return strip(/\\?\"/g, str);
},
trim = function (str, len) {
if (isString(str)) {
str = strip(/^\s\s*/, str);
return typeof len === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH);
}
str = strip(/^\s\s*/, String(str));
return typeof len === TYPEOF.UNDEFINED ? str : str.substring(0, len);
};
///////////////
@@ -240,9 +238,9 @@
match = matches[++k];
q = props[p];
// check if given property is actually array
if (typeof q === OBJ_TYPE && q.length > 0) {
if (typeof q === TYPEOF.OBJECT && q.length > 0) {
if (q.length === 2) {
if (typeof q[1] == FUNC_TYPE) {
if (typeof q[1] == TYPEOF.FUNCTION) {
// assign modified match
this[q[0]] = q[1].call(this, match);
} else {
@@ -251,7 +249,7 @@
}
} else if (q.length >= 3) {
// Check whether q[1] FUNCTION or REGEX
if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
if (typeof q[1] === TYPEOF.FUNCTION && !(q[1].exec && q[1].test)) {
if (q.length > 3) {
this[q[0]] = match ? q[1].apply(this, q.slice(2)) : undefined;
} else {
@@ -283,7 +281,7 @@
for (var i in map) {
// check if current value is array
if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
if (typeof map[i] === TYPEOF.OBJECT && map[i].length > 0) {
for (var j = 0; j < map[i].length; j++) {
if (has(map[i][j], str)) {
return (i === UNKNOWN) ? undefined : i;
@@ -377,10 +375,12 @@
/(avant|iemobile|slim(?:browser|boat|jet))[\/ ]?([\d\.]*)/i, // Avant/IEMobile/SlimBrowser/SlimBoat/Slimjet
/(?: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
/(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)\/([-\w\.]+)/i,
// Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(heytap|ovi|115|surf)browser\/([\d\.]+)/i, // HeyTap/Ovi/115/Surf
// Blink/Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon/LG Browser/Otter/qutebrowser/Dooble/Palemoon/HiBrowser
/(atlas|flock|rockmelt|midori|epiphany|silk|skyfire|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon|otter|dooble|(?:hi|lg |ovi|qute)browser|palemoon)\/v?([-\w\.]+)/i,
// Atlas/Rekonq/Puffin/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(brave)(?: chrome)?\/([\d\.]+)/i, // Brave
/(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
], [NAME, VERSION], [
/quark(?:pc)?\/([-\w\.]+)/i // Quark
@@ -405,7 +405,9 @@
], [[NAME, /(.+)/, '$1 Secure' + SUFFIX_BROWSER], VERSION], [
/\bfocus\/([\w\.]+)/i // Firefox Focus
], [VERSION, [NAME, FIREFOX+' Focus']], [
/\bopt\/([\w\.]+)/i // Opera Touch
/ mms\/([\w\.]+)$/i // Opera Neon
], [VERSION, [NAME, OPERA+' Neon']], [
/ opt\/([\w\.]+)$/i // Opera Touch
], [VERSION, [NAME, OPERA+' Touch']], [
/coc_coc\w+\/([\w\.]+)/i // Coc Coc Browser
], [VERSION, [NAME, 'Coc Coc']], [
@@ -433,7 +435,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
@@ -449,11 +451,12 @@
/\b(line)\/([\w\.]+)\/iab/i, // Line App for Android
/(alipay)client\/([\w\.]+)/i, // Alipay
/(twitter)(?:and| f.+e\/([\w\.]+))/i, // Twitter
/(bing)(?:web|sapphire)\/([\w\.]+)/i, // Bing
/(instagram|snapchat|klarna)[\/ ]([-\w\.]+)/i // Instagram/Snapchat/Klarna
], [NAME, VERSION, [TYPE, INAPP]], [
/\bgsa\/([\w\.]+) .*safari\//i // Google Search Appliance on iOS
], [VERSION, [NAME, 'GSA'], [TYPE, INAPP]], [
/musical_ly(?:.+app_?version\/|_)([\w\.]+)/i // TikTok
/(?:musical_ly|trill)(?:.+app_?version\/|_)([\w\.]+)/i // TikTok
], [VERSION, [NAME, 'TikTok'], [TYPE, INAPP]], [
/\[(linkedin)app\]/i // LinkedIn App for iOS & Android
], [NAME, [TYPE, INAPP]], [
@@ -506,10 +509,10 @@
/(swiftfox)/i, // Swiftfox
/(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror)[\/ ]?([\w\.\+]+)/i,
// IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
/(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w\.]+)$/i,
/(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|basilisk|waterfox)\/([-\w\.]+)$/i,
// Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
/(firefox)\/([\w\.]+)/i, // Other Firefox-based
/(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i, // Mozilla
/(mozilla)\/([\w\.]+(?= .+rv\:.+gecko\/\d+)|[0-4][\w\.]+(?!.+compatible))/i, // Mozilla
// Other
/(amaya|dillo|doris|icab|ladybird|lynx|mosaic|netsurf|obigo|polaris|w3m|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
@@ -547,6 +550,8 @@
/((ppc|powerpc)(64)?)( mac|;|\))/i, // PowerPC
/(?:osf1|[freopnt]{3,4}bsd) (alpha)/i // Alpha
], [[ARCHITECTURE, /ower/, EMPTY, lowerize]], [
/mc680.0/i
], [[ARCHITECTURE, '68k']], [
/winnt.+\[axp/i
], [[ARCHITECTURE, 'alpha']]
],
@@ -566,11 +571,10 @@
], [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]], [
// Apple
/(?:\/|\()(ip(?:hone|od)[\w, ]*)(?:\/|;)/i // iPod/iPhone
/(?:\/|\()(ip(?:hone|od)[\w, ]*)[\/\);]/i // iPod/iPhone
], [MODEL, [VENDOR, APPLE], [TYPE, MOBILE]], [
/\((ipad);[-\w\),; ]+apple/i, // iPad
/applecoremedia\/[\w\.]+ \((ipad)/i,
/\b(ipad)\d\d?,\d\d?[;\]].+ios/i
/\b(?:ios|apple\w+)\/.+[\(\/](ipad)/i, // iPad
/\b(ipad)[\d,]*[;\] ].+(mac |i(pad)?)os/i
], [MODEL, [VENDOR, APPLE], [TYPE, TABLET]], [
/(macintosh);/i
], [MODEL, [VENDOR, APPLE]], [
@@ -588,26 +592,26 @@
// Huawei
/\b((?:ag[rs][2356]?k?|bah[234]?|bg[2o]|bt[kv]|cmr|cpn|db[ry]2?|jdn2|got|kob2?k?|mon|pce|scm|sht?|[tw]gr|vrd)-[ad]?[lw][0125][09]b?|605hw|bg2-u03|(?:gem|fdr|m2|ple|t1)-[7a]0[1-4][lu]|t1-a2[13][lw]|mediapad[\w\. ]*(?= bui|\)))\b(?!.+d\/s)/i
], [MODEL, [VENDOR, HUAWEI], [TYPE, TABLET]], [
/(?:huawei)([-\w ]+)[;\)]/i,
/\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][012359c][adn]?)\b(?!.+d\/s)/i
/(?:huawei) ?([-\w ]+)[;\)]/i,
/\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][\dc][adnt]?)\b(?!.+d\/s)/i
], [MODEL, [VENDOR, HUAWEI], [TYPE, MOBILE]], [
// Xiaomi
/oid[^\)]+; (2[\dbc]{4}(182|283|rp\w{2})[cgl]|m2105k81a?c)(?: bui|\))/i,
/\b((?:red)?mi[-_ ]?pad[\w- ]*)(?: bui|\))/i // Mi Pad tablets
/\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 lte|max|cc)?[_ ]?(?:\d?\w?)[_ ]?(?:plus|se|lite|pro)?)(?: 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]], [
// OnePlus
/droid.+; (cph2[3-6]\d[13579]|((gm|hd)19|(ac|be|in|kb)20|(d[en]|eb|le|mt)21|ne22)[0-2]\d|p[g-k]\w[1m]10)\b/i,
/droid.+; (cph2[3-6]\d[13579]|((gm|hd)19|(ac|be|in|kb)20|(d[en]|eb|le|mt)21|ne22)[0-2]\d|p[g-l]\w[1m]10)\b/i,
/(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i
], [MODEL, [VENDOR, ONEPLUS], [TYPE, MOBILE]], [
@@ -647,7 +651,7 @@
], [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]], [
// LG
/((?=lg)?[vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i
/\b(?:lg)?([vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i
], [MODEL, [VENDOR, LG], [TYPE, TABLET]], [
/(lm(?:-?f100[nv]?|-[\w\.]+)(?= bui|\))|nexus [45])/i,
/\blg[-e;\/ ]+(?!.*(?:browser|netcast|android tv|watch|webos))(\w+)/i,
@@ -658,7 +662,7 @@
/(nokia) (t[12][01])/i
], [VENDOR, MODEL, [TYPE, TABLET]], [
/(?:maemo|nokia).*(n900|lumia \d+|rm-\d+)/i,
/nokia[-_ ]?(([-\w\. ]*))/i
/nokia[-_ ]?(([-\w\. ]*?))( bui|\)|;|\/)/i
], [[MODEL, /_/g, ' '], [TYPE, MOBILE], [VENDOR, 'Nokia']], [
// Google
@@ -689,7 +693,7 @@
/(playbook);[-\w\),; ]+(rim)/i // BlackBerry PlayBook
], [MODEL, VENDOR, [TYPE, TABLET]], [
/\b((?:bb[a-f]|st[hv])100-\d)/i,
/\(bb10; (\w+)/i // BlackBerry 10
/(?:blackberry|\(bb10;) (\w+)/i
], [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]], [
// Asus
@@ -769,10 +773,12 @@
/(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus(?! zenw)|dell|jolla|meizu|motorola|polytron|tecno|micromax|advan)[-_ ]?([-\w]*)/i,
// BlackBerry/BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron/Tecno/Micromax/Advan
/; (blu|hmd|imo|infinix|lava|oneplus|tcl)[_ ]([\w\+ ]+?)(?: bui|\)|; r)/i, // BLU/HMD/IMO/Infinix/Lava/OnePlus/TCL
// BLU/HMD/IMO/Infinix/Lava/OnePlus/TCL/Wiko
/; (blu|hmd|imo|infinix|lava|oneplus|tcl|wiko)[_ ]([\w\+ ]+?)(?: bui|\)|; r)/i,
/(hp) ([\w ]+\w)/i, // HP iPAQ
/(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia
/(oppo) ?([\w ]+) bui/i, // OPPO
/(hisense) ([ehv][\w ]+)\)/i, // Hisense
/droid[^;]+; (philips)[_ ]([sv-x][\d]{3,4}[xz]?)/i // Philips
], [VENDOR, MODEL, [TYPE, MOBILE]], [
@@ -849,10 +855,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
@@ -861,7 +867,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]], [
@@ -912,7 +918,7 @@
], [MODEL, [TYPE, SMARTTV]], [
/\b((4k|android|smart|opera)[- ]?tv|tv; rv:|large screen[\w ]+safari)\b/i
], [[TYPE, SMARTTV]], [
/droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew).+?(mobile|vr|\d) safari/i
/droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew|; hmsc).+?(mobile|vr|\d) safari/i
], [MODEL, [TYPE, strMapper, { 'mobile' : 'Mobile', 'xr' : 'VR', '*' : TABLET }]], [
/\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i // Unidentifiable Tablet
], [[TYPE, TABLET]], [
@@ -936,7 +942,7 @@
/(presto)\/([\w\.]+)/i, // Presto
/(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna|servo)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna/Servo
/ekioh(flow)\/([\w\.]+)/i, // Flow
/(khtml|tasman|links)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links
/(khtml|tasman|links|dillo)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links/Dillo
/(icab)[\/ ]([23]\.[\d\.]+)/i, // iCab
/\b(libweb)/i // LibWeb
@@ -965,7 +971,8 @@
// iOS/macOS
/[adehimnop]{4,7}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
/(?:ios;fbsv\/|iphone.+ios[\/ ])([\d\.]+)/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,
@@ -997,7 +1004,7 @@
], [VERSION, [NAME, BLACKBERRY]], [
/(?:symbian ?os|symbos|s60(?=;)|series ?60)[-\/ ]?([\w\.]*)/i // Symbian
], [VERSION, [NAME, 'Symbian']], [
/mozilla\/[\d\.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w\.]+)/i // Firefox OS
/mozilla\/[\d\.]+ \((?:mobile[;\w ]*|tablet|tv|[^\)]*(?:viera|lg(?:l25|-d300)|alcatel ?o.+|y300-f1)); rv:([\w\.]+)\).+gecko\//i // Firefox OS
], [VERSION, [NAME, FIREFOX+' OS']], [
/\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i, // WebOS
/webos(?:[ \/]?|\.tv-20(?=2[2-9]))(\d[\d\.]*)/i
@@ -1013,7 +1020,6 @@
], [[NAME, "Chrome OS"], VERSION],[
// Smart TVs
/panasonic;(viera)/i, // Panasonic Viera
/(netrange)mmh/i, // Netrange
/(nettv)\/(\d+\.[\w\.]+)/i, // NetTV
@@ -1050,27 +1056,27 @@
var defaultProps = (function () {
var props = { init : {}, isIgnore : {}, isIgnoreRgx : {}, toString : {}};
setProps.call(props.init, [
[UA_BROWSER, [NAME, VERSION, MAJOR, TYPE]],
[UA_CPU, [ARCHITECTURE]],
[UA_DEVICE, [TYPE, MODEL, VENDOR]],
[UA_ENGINE, [NAME, VERSION]],
[UA_OS, [NAME, VERSION]]
[BROWSER, [NAME, VERSION, MAJOR, TYPE]],
[CPU, [ARCHITECTURE]],
[DEVICE, [TYPE, MODEL, VENDOR]],
[ENGINE, [NAME, VERSION]],
[OS, [NAME, VERSION]]
]);
setProps.call(props.isIgnore, [
[UA_BROWSER, [VERSION, MAJOR]],
[UA_ENGINE, [VERSION]],
[UA_OS, [VERSION]]
[BROWSER, [VERSION, MAJOR]],
[ENGINE, [VERSION]],
[OS, [VERSION]]
]);
setProps.call(props.isIgnoreRgx, [
[UA_BROWSER, / ?browser$/i],
[UA_OS, / ?os$/i]
[BROWSER, / ?browser$/i],
[OS, / ?os$/i]
]);
setProps.call(props.toString, [
[UA_BROWSER, [NAME, VERSION]],
[UA_CPU, [ARCHITECTURE]],
[UA_DEVICE, [VENDOR, MODEL]],
[UA_ENGINE, [NAME, VERSION]],
[UA_OS, [NAME, VERSION]]
[BROWSER, [NAME, VERSION]],
[CPU, [ARCHITECTURE]],
[DEVICE, [VENDOR, MODEL]],
[ENGINE, [NAME, VERSION]],
[OS, [NAME, VERSION]]
]);
return props;
})();
@@ -1114,14 +1120,14 @@
return item.detectFeature().get();
};
if (itemType != UA_RESULT) {
if (itemType != RESULT) {
IData.prototype.is = function (strToCheck) {
var is = false;
for (var i in this) {
if (this.hasOwnProperty(i) && !has(is_ignoreProps, i) && lowerize(is_ignoreRgx ? strip(is_ignoreRgx, this[i]) : this[i]) == lowerize(is_ignoreRgx ? strip(is_ignoreRgx, strToCheck) : strToCheck)) {
is = true;
if (strToCheck != UNDEF_TYPE) break;
} else if (strToCheck == UNDEF_TYPE && is) {
if (strToCheck != TYPEOF.UNDEFINED) break;
} else if (strToCheck == TYPEOF.UNDEFINED && is) {
is = !is;
break;
}
@@ -1131,33 +1137,33 @@
IData.prototype.toString = function () {
var str = EMPTY;
for (var i in toString_props) {
if (typeof(this[toString_props[i]]) !== UNDEF_TYPE) {
if (typeof(this[toString_props[i]]) !== TYPEOF.UNDEFINED) {
str += (str ? ' ' : EMPTY) + this[toString_props[i]];
}
}
return str || UNDEF_TYPE;
return str || TYPEOF.UNDEFINED;
};
}
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();
};
@@ -1171,196 +1177,24 @@
setProps.call(this, CH_ALL_VALUES);
if (isHttpUACH) {
setProps.call(this, [
[BRANDS, itemListToArray(uach[CH_HEADER])],
[FULLVERLIST, itemListToArray(uach[CH_HEADER_FULL_VER_LIST])],
[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])],
[FORMFACTORS, itemListToArray(uach[CH_HEADER_FORM_FACTORS])],
[BITNESS, stripQuotes(uach[CH_HEADER_BITNESS])]
[BRANDS, itemListToArray(uach[CH])],
[FULLVERLIST, itemListToArray(uach[CH_FULL_VER_LIST])],
[MOBILE, /\?1/.test(uach[CH_MOBILE])],
[MODEL, stripQuotes(uach[CH_MODEL])],
[PLATFORM, stripQuotes(uach[CH_PLATFORM])],
[PLATFORMVER, stripQuotes(uach[CH_PLATFORM_VER])],
[ARCHITECTURE, stripQuotes(uach[CH_ARCH])],
[FORMFACTORS, itemListToArray(uach[CH_FORM_FACTORS])],
[BITNESS, stripQuotes(uach[CH_BITNESS])]
]);
} else {
for (var prop in uach) {
if(this.hasOwnProperty(prop) && typeof uach[prop] !== UNDEF_TYPE) this[prop] = uach[prop];
if(this.hasOwnProperty(prop) && typeof uach[prop] !== TYPEOF.UNDEFINED) this[prop] = uach[prop];
}
}
}
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 UA_BROWSER:
// Brave-specific detection
if (NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == FUNC_TYPE) {
this.set(NAME, 'Brave');
}
break;
case UA_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 !== UNDEF_TYPE && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) {
this.set(MODEL, 'iPad')
.set(TYPE, TABLET);
}
break;
case UA_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 UA_RESULT:
var data = this.data;
var detect = function (itemType) {
return data[itemType]
.getItem()
.detectFeature()
.get();
};
this.set(UA_BROWSER, detect(UA_BROWSER))
.set(UA_CPU, detect(UA_CPU))
.set(UA_DEVICE, detect(UA_DEVICE))
.set(UA_ENGINE, detect(UA_ENGINE))
.set(UA_OS, detect(UA_OS));
}
}
return this;
};
this.parseUA = function () {
if (this.itemType != UA_RESULT) {
rgxMapper.call(this.data, this.ua, this.rgxMap);
}
if (this.itemType == UA_BROWSER) {
this.set(MAJOR, majorize(this.get(VERSION)));
}
return this;
};
this.parseCH = function () {
var uaCH = this.uaCH,
rgxMap = this.rgxMap;
switch (this.itemType) {
case UA_BROWSER:
case UA_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 == UA_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 == UA_ENGINE && brandName == CHROMIUM) {
this.set(VERSION, brandVersion);
}
}
}
break;
case UA_CPU:
var archName = uaCH[ARCHITECTURE];
if (archName) {
if (archName && uaCH[BITNESS] == '64') archName += '64';
rgxMapper.call(this.data, archName + ';', rgxMap);
}
break;
case UA_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 UA_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 UA_RESULT:
var data = this.data;
var parse = function (itemType) {
return data[itemType]
.getItem()
.setCH(uaCH)
.parseCH()
.get();
};
this.set(UA_BROWSER, parse(UA_BROWSER))
.set(UA_CPU, parse(UA_CPU))
.set(UA_DEVICE, parse(UA_DEVICE))
.set(UA_ENGINE, parse(UA_ENGINE))
.set(UA_OS, parse(UA_OS));
}
return this;
};
setProps.call(this, [
['itemType', itemType],
['ua', ua],
@@ -1368,15 +1202,196 @@
['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 === OBJ_TYPE) {
if (typeof ua === TYPEOF.OBJECT) {
if (isExtensions(ua, true)) {
if (typeof extensions === OBJ_TYPE) {
if (typeof extensions === TYPEOF.OBJECT) {
headers = extensions; // case UAParser(extensions, headers)
}
extensions = ua; // case UAParser(extensions)
@@ -1385,13 +1400,13 @@
extensions = undefined;
}
ua = undefined;
} else if (typeof ua === STR_TYPE && !isExtensions(extensions, true)) {
} else if (typeof ua === TYPEOF.STRING && !isExtensions(extensions, true)) {
headers = extensions; // case UAParser(ua, headers)
extensions = undefined;
}
if (headers) {
if (typeof headers.append === FUNC_TYPE) {
if (typeof headers.append === TYPEOF.FUNCTION) {
// Convert Headers object into a plain object
var kv = {};
headers.forEach(function (v, k) { kv[String(k).toLowerCase()] = v; });
@@ -1412,7 +1427,7 @@
return new UAParser(ua, extensions, headers).getResult();
}
var userAgent = typeof ua === STR_TYPE ? ua : // Passed user-agent string
var userAgent = typeof ua === TYPEOF.STRING ? ua : // Passed user-agent string
(headers && headers[USER_AGENT] ? headers[USER_AGENT] : // User-Agent from passed headers
((NAVIGATOR && NAVIGATOR.userAgent) ? NAVIGATOR.userAgent : // navigator.userAgent
EMPTY)), // empty string
@@ -1423,15 +1438,15 @@
defaultRegexes,
createItemFunc = function (itemType) {
if (itemType == UA_RESULT) {
if (itemType == RESULT) {
return function () {
return new UAItem(itemType, userAgent, regexMap, httpUACH)
.set('ua', userAgent)
.set(UA_BROWSER, this.getBrowser())
.set(UA_CPU, this.getCPU())
.set(UA_DEVICE, this.getDevice())
.set(UA_ENGINE, this.getEngine())
.set(UA_OS, this.getOS())
.set(BROWSER, this.getBrowser())
.set(CPU, this.getCPU())
.set(DEVICE, this.getDevice())
.set(ENGINE, this.getEngine())
.set(OS, this.getOS())
.get();
};
} else {
@@ -1445,16 +1460,15 @@
// public methods
setProps.call(this, [
['getBrowser', createItemFunc(UA_BROWSER)],
['getCPU', createItemFunc(UA_CPU)],
['getDevice', createItemFunc(UA_DEVICE)],
['getEngine', createItemFunc(UA_ENGINE)],
['getOS', createItemFunc(UA_OS)],
['getResult', createItemFunc(UA_RESULT)],
['getBrowser', createItemFunc(BROWSER)],
['getCPU', createItemFunc(CPU)],
['getDevice', createItemFunc(DEVICE)],
['getEngine', createItemFunc(ENGINE)],
['getOS', createItemFunc(OS)],
['getResult', createItemFunc(RESULT)],
['getUA', function () { return userAgent; }],
['setUA', function (ua) {
if (isString(ua))
userAgent = ua.length > UA_MAX_LENGTH ? trim(ua, UA_MAX_LENGTH) : ua;
if (isString(ua)) userAgent = trim(ua, UA_MAX_LENGTH);
return this;
}]
])
@@ -1474,15 +1488,15 @@
//////////
// check js environment
if (typeof exports !== UNDEF_TYPE) {
if (typeof exports !== TYPEOF.UNDEFINED) {
// nodejs env
if (typeof module !== UNDEF_TYPE && module.exports) {
if (typeof module !== TYPEOF.UNDEFINED && module.exports) {
exports = module.exports = UAParser;
}
exports.UAParser = UAParser;
} else {
// requirejs env (optional)
if (typeof define === FUNC_TYPE && define.amd) {
if (typeof define === TYPEOF.FUNCTION && define.amd) {
define(function () {
return UAParser;
});

View File

@@ -3,7 +3,7 @@
// Source: /src/main/ua-parser.js
/////////////////////////////////////////////////////////////////////////////////
/* UAParser.js v2.0.5
/* UAParser.js v2.0.8
Copyright © 2012-2025 Faisal Salman <f@faisalman.com>
AGPLv3 License *//*
Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
@@ -21,25 +21,25 @@
// Constants
/////////////
var LIBVERSION = '2.0.5',
var LIBVERSION = '2.0.8',
UA_MAX_LENGTH = 500,
USER_AGENT = 'user-agent',
EMPTY = '',
UNKNOWN = '?',
// typeof
FUNC_TYPE = 'function',
UNDEF_TYPE = 'undefined',
OBJ_TYPE = 'object',
STR_TYPE = 'string',
TYPEOF = {
FUNCTION : 'function',
OBJECT : 'object',
STRING : 'string',
UNDEFINED : 'undefined'
},
// properties
UA_BROWSER = 'browser',
UA_CPU = 'cpu',
UA_DEVICE = 'device',
UA_ENGINE = 'engine',
UA_OS = 'os',
UA_RESULT = 'result',
BROWSER = 'browser',
CPU = 'cpu',
DEVICE = 'device',
ENGINE = 'engine',
OS = 'os',
RESULT = 'result',
NAME = 'name',
TYPE = 'type',
@@ -68,16 +68,16 @@
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',
CH_HEADER_BITNESS = CH_HEADER + '-' + BITNESS,
CH_HEADER_FORM_FACTORS = CH_HEADER + '-form-factors',
CH_HEADER_MOBILE = CH_HEADER + '-' + MOBILE,
CH_HEADER_MODEL = CH_HEADER + '-' + MODEL,
CH_HEADER_PLATFORM = CH_HEADER + '-' + PLATFORM,
CH_HEADER_PLATFORM_VER = CH_HEADER_PLATFORM + '-version',
CH_ALL_VALUES = [BRANDS, FULLVERLIST, MOBILE, MODEL, PLATFORM, PLATFORMVER, ARCHITECTURE, FORMFACTORS, BITNESS],
CH = 'sec-ch-ua',
CH_FULL_VER_LIST= CH + '-full-version-list',
CH_ARCH = CH + '-arch',
CH_BITNESS = CH + '-' + BITNESS,
CH_FORM_FACTORS = CH + '-form-factors',
CH_MOBILE = CH + '-' + MOBILE,
CH_MODEL = CH + '-' + MODEL,
CH_PLATFORM = CH + '-' + PLATFORM,
CH_PLATFORM_VER = CH_PLATFORM + '-version',
CH_ALL_VALUES = [BRANDS, FULLVERLIST, MOBILE, MODEL, PLATFORM, PLATFORMVER, ARCHITECTURE, FORMFACTORS, BITNESS],
// device vendors
AMAZON = 'Amazon',
@@ -116,7 +116,7 @@
// os
WINDOWS = 'Windows';
var isWindow = typeof window !== UNDEF_TYPE,
var isWindow = typeof window !== TYPEOF.UNDEFINED,
NAVIGATOR = (isWindow && window.navigator) ?
window.navigator :
undefined,
@@ -152,7 +152,7 @@
return enums;
},
has = function (str1, str2) {
if (typeof str1 === OBJ_TYPE && str1.length > 0) {
if (typeof str1 === TYPEOF.OBJECT && str1.length > 0) {
for (var i in str1) {
if (lowerize(str2) == lowerize(str1[i])) return true;
}
@@ -166,7 +166,7 @@
}
},
isString = function (val) {
return typeof val === STR_TYPE;
return typeof val === TYPEOF.STRING;
},
itemListToArray = function (header) {
if (!header) return undefined;
@@ -193,7 +193,7 @@
if (!arr.hasOwnProperty(i)) continue;
var propName = arr[i];
if (typeof propName == OBJ_TYPE && propName.length == 2) {
if (typeof propName == TYPEOF.OBJECT && propName.length == 2) {
this[propName[0]] = propName[1];
} else {
this[propName] = undefined;
@@ -208,10 +208,8 @@
return strip(/\\?\"/g, str);
},
trim = function (str, len) {
if (isString(str)) {
str = strip(/^\s\s*/, str);
return typeof len === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH);
}
str = strip(/^\s\s*/, String(str));
return typeof len === TYPEOF.UNDEFINED ? str : str.substring(0, len);
};
///////////////
@@ -242,9 +240,9 @@
match = matches[++k];
q = props[p];
// check if given property is actually array
if (typeof q === OBJ_TYPE && q.length > 0) {
if (typeof q === TYPEOF.OBJECT && q.length > 0) {
if (q.length === 2) {
if (typeof q[1] == FUNC_TYPE) {
if (typeof q[1] == TYPEOF.FUNCTION) {
// assign modified match
this[q[0]] = q[1].call(this, match);
} else {
@@ -253,7 +251,7 @@
}
} else if (q.length >= 3) {
// Check whether q[1] FUNCTION or REGEX
if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
if (typeof q[1] === TYPEOF.FUNCTION && !(q[1].exec && q[1].test)) {
if (q.length > 3) {
this[q[0]] = match ? q[1].apply(this, q.slice(2)) : undefined;
} else {
@@ -285,7 +283,7 @@
for (var i in map) {
// check if current value is array
if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
if (typeof map[i] === TYPEOF.OBJECT && map[i].length > 0) {
for (var j = 0; j < map[i].length; j++) {
if (has(map[i][j], str)) {
return (i === UNKNOWN) ? undefined : i;
@@ -379,10 +377,12 @@
/(avant|iemobile|slim(?:browser|boat|jet))[\/ ]?([\d\.]*)/i, // Avant/IEMobile/SlimBrowser/SlimBoat/Slimjet
/(?: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
/(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)\/([-\w\.]+)/i,
// Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(heytap|ovi|115|surf)browser\/([\d\.]+)/i, // HeyTap/Ovi/115/Surf
// Blink/Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon/LG Browser/Otter/qutebrowser/Dooble/Palemoon/HiBrowser
/(atlas|flock|rockmelt|midori|epiphany|silk|skyfire|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon|otter|dooble|(?:hi|lg |ovi|qute)browser|palemoon)\/v?([-\w\.]+)/i,
// Atlas/Rekonq/Puffin/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(brave)(?: chrome)?\/([\d\.]+)/i, // Brave
/(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
], [NAME, VERSION], [
/quark(?:pc)?\/([-\w\.]+)/i // Quark
@@ -407,7 +407,9 @@
], [[NAME, /(.+)/, '$1 Secure' + SUFFIX_BROWSER], VERSION], [
/\bfocus\/([\w\.]+)/i // Firefox Focus
], [VERSION, [NAME, FIREFOX+' Focus']], [
/\bopt\/([\w\.]+)/i // Opera Touch
/ mms\/([\w\.]+)$/i // Opera Neon
], [VERSION, [NAME, OPERA+' Neon']], [
/ opt\/([\w\.]+)$/i // Opera Touch
], [VERSION, [NAME, OPERA+' Touch']], [
/coc_coc\w+\/([\w\.]+)/i // Coc Coc Browser
], [VERSION, [NAME, 'Coc Coc']], [
@@ -435,7 +437,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
@@ -451,11 +453,12 @@
/\b(line)\/([\w\.]+)\/iab/i, // Line App for Android
/(alipay)client\/([\w\.]+)/i, // Alipay
/(twitter)(?:and| f.+e\/([\w\.]+))/i, // Twitter
/(bing)(?:web|sapphire)\/([\w\.]+)/i, // Bing
/(instagram|snapchat|klarna)[\/ ]([-\w\.]+)/i // Instagram/Snapchat/Klarna
], [NAME, VERSION, [TYPE, INAPP]], [
/\bgsa\/([\w\.]+) .*safari\//i // Google Search Appliance on iOS
], [VERSION, [NAME, 'GSA'], [TYPE, INAPP]], [
/musical_ly(?:.+app_?version\/|_)([\w\.]+)/i // TikTok
/(?:musical_ly|trill)(?:.+app_?version\/|_)([\w\.]+)/i // TikTok
], [VERSION, [NAME, 'TikTok'], [TYPE, INAPP]], [
/\[(linkedin)app\]/i // LinkedIn App for iOS & Android
], [NAME, [TYPE, INAPP]], [
@@ -508,10 +511,10 @@
/(swiftfox)/i, // Swiftfox
/(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror)[\/ ]?([\w\.\+]+)/i,
// IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
/(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w\.]+)$/i,
/(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|basilisk|waterfox)\/([-\w\.]+)$/i,
// Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
/(firefox)\/([\w\.]+)/i, // Other Firefox-based
/(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i, // Mozilla
/(mozilla)\/([\w\.]+(?= .+rv\:.+gecko\/\d+)|[0-4][\w\.]+(?!.+compatible))/i, // Mozilla
// Other
/(amaya|dillo|doris|icab|ladybird|lynx|mosaic|netsurf|obigo|polaris|w3m|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
@@ -549,6 +552,8 @@
/((ppc|powerpc)(64)?)( mac|;|\))/i, // PowerPC
/(?:osf1|[freopnt]{3,4}bsd) (alpha)/i // Alpha
], [[ARCHITECTURE, /ower/, EMPTY, lowerize]], [
/mc680.0/i
], [[ARCHITECTURE, '68k']], [
/winnt.+\[axp/i
], [[ARCHITECTURE, 'alpha']]
],
@@ -568,11 +573,10 @@
], [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]], [
// Apple
/(?:\/|\()(ip(?:hone|od)[\w, ]*)(?:\/|;)/i // iPod/iPhone
/(?:\/|\()(ip(?:hone|od)[\w, ]*)[\/\);]/i // iPod/iPhone
], [MODEL, [VENDOR, APPLE], [TYPE, MOBILE]], [
/\((ipad);[-\w\),; ]+apple/i, // iPad
/applecoremedia\/[\w\.]+ \((ipad)/i,
/\b(ipad)\d\d?,\d\d?[;\]].+ios/i
/\b(?:ios|apple\w+)\/.+[\(\/](ipad)/i, // iPad
/\b(ipad)[\d,]*[;\] ].+(mac |i(pad)?)os/i
], [MODEL, [VENDOR, APPLE], [TYPE, TABLET]], [
/(macintosh);/i
], [MODEL, [VENDOR, APPLE]], [
@@ -590,26 +594,26 @@
// Huawei
/\b((?:ag[rs][2356]?k?|bah[234]?|bg[2o]|bt[kv]|cmr|cpn|db[ry]2?|jdn2|got|kob2?k?|mon|pce|scm|sht?|[tw]gr|vrd)-[ad]?[lw][0125][09]b?|605hw|bg2-u03|(?:gem|fdr|m2|ple|t1)-[7a]0[1-4][lu]|t1-a2[13][lw]|mediapad[\w\. ]*(?= bui|\)))\b(?!.+d\/s)/i
], [MODEL, [VENDOR, HUAWEI], [TYPE, TABLET]], [
/(?:huawei)([-\w ]+)[;\)]/i,
/\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][012359c][adn]?)\b(?!.+d\/s)/i
/(?:huawei) ?([-\w ]+)[;\)]/i,
/\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][\dc][adnt]?)\b(?!.+d\/s)/i
], [MODEL, [VENDOR, HUAWEI], [TYPE, MOBILE]], [
// Xiaomi
/oid[^\)]+; (2[\dbc]{4}(182|283|rp\w{2})[cgl]|m2105k81a?c)(?: bui|\))/i,
/\b((?:red)?mi[-_ ]?pad[\w- ]*)(?: bui|\))/i // Mi Pad tablets
/\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 lte|max|cc)?[_ ]?(?:\d?\w?)[_ ]?(?:plus|se|lite|pro)?)(?: 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]], [
// OnePlus
/droid.+; (cph2[3-6]\d[13579]|((gm|hd)19|(ac|be|in|kb)20|(d[en]|eb|le|mt)21|ne22)[0-2]\d|p[g-k]\w[1m]10)\b/i,
/droid.+; (cph2[3-6]\d[13579]|((gm|hd)19|(ac|be|in|kb)20|(d[en]|eb|le|mt)21|ne22)[0-2]\d|p[g-l]\w[1m]10)\b/i,
/(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i
], [MODEL, [VENDOR, ONEPLUS], [TYPE, MOBILE]], [
@@ -649,7 +653,7 @@
], [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]], [
// LG
/((?=lg)?[vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i
/\b(?:lg)?([vl]k\-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i
], [MODEL, [VENDOR, LG], [TYPE, TABLET]], [
/(lm(?:-?f100[nv]?|-[\w\.]+)(?= bui|\))|nexus [45])/i,
/\blg[-e;\/ ]+(?!.*(?:browser|netcast|android tv|watch|webos))(\w+)/i,
@@ -660,7 +664,7 @@
/(nokia) (t[12][01])/i
], [VENDOR, MODEL, [TYPE, TABLET]], [
/(?:maemo|nokia).*(n900|lumia \d+|rm-\d+)/i,
/nokia[-_ ]?(([-\w\. ]*))/i
/nokia[-_ ]?(([-\w\. ]*?))( bui|\)|;|\/)/i
], [[MODEL, /_/g, ' '], [TYPE, MOBILE], [VENDOR, 'Nokia']], [
// Google
@@ -691,7 +695,7 @@
/(playbook);[-\w\),; ]+(rim)/i // BlackBerry PlayBook
], [MODEL, VENDOR, [TYPE, TABLET]], [
/\b((?:bb[a-f]|st[hv])100-\d)/i,
/\(bb10; (\w+)/i // BlackBerry 10
/(?:blackberry|\(bb10;) (\w+)/i
], [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]], [
// Asus
@@ -771,10 +775,12 @@
/(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus(?! zenw)|dell|jolla|meizu|motorola|polytron|tecno|micromax|advan)[-_ ]?([-\w]*)/i,
// BlackBerry/BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron/Tecno/Micromax/Advan
/; (blu|hmd|imo|infinix|lava|oneplus|tcl)[_ ]([\w\+ ]+?)(?: bui|\)|; r)/i, // BLU/HMD/IMO/Infinix/Lava/OnePlus/TCL
// BLU/HMD/IMO/Infinix/Lava/OnePlus/TCL/Wiko
/; (blu|hmd|imo|infinix|lava|oneplus|tcl|wiko)[_ ]([\w\+ ]+?)(?: bui|\)|; r)/i,
/(hp) ([\w ]+\w)/i, // HP iPAQ
/(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia
/(oppo) ?([\w ]+) bui/i, // OPPO
/(hisense) ([ehv][\w ]+)\)/i, // Hisense
/droid[^;]+; (philips)[_ ]([sv-x][\d]{3,4}[xz]?)/i // Philips
], [VENDOR, MODEL, [TYPE, MOBILE]], [
@@ -851,10 +857,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
@@ -863,7 +869,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]], [
@@ -914,7 +920,7 @@
], [MODEL, [TYPE, SMARTTV]], [
/\b((4k|android|smart|opera)[- ]?tv|tv; rv:|large screen[\w ]+safari)\b/i
], [[TYPE, SMARTTV]], [
/droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew).+?(mobile|vr|\d) safari/i
/droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew|; hmsc).+?(mobile|vr|\d) safari/i
], [MODEL, [TYPE, strMapper, { 'mobile' : 'Mobile', 'xr' : 'VR', '*' : TABLET }]], [
/\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i // Unidentifiable Tablet
], [[TYPE, TABLET]], [
@@ -938,7 +944,7 @@
/(presto)\/([\w\.]+)/i, // Presto
/(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna|servo)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna/Servo
/ekioh(flow)\/([\w\.]+)/i, // Flow
/(khtml|tasman|links)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links
/(khtml|tasman|links|dillo)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links/Dillo
/(icab)[\/ ]([23]\.[\d\.]+)/i, // iCab
/\b(libweb)/i // LibWeb
@@ -967,7 +973,8 @@
// iOS/macOS
/[adehimnop]{4,7}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
/(?:ios;fbsv\/|iphone.+ios[\/ ])([\d\.]+)/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,
@@ -999,7 +1006,7 @@
], [VERSION, [NAME, BLACKBERRY]], [
/(?:symbian ?os|symbos|s60(?=;)|series ?60)[-\/ ]?([\w\.]*)/i // Symbian
], [VERSION, [NAME, 'Symbian']], [
/mozilla\/[\d\.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w\.]+)/i // Firefox OS
/mozilla\/[\d\.]+ \((?:mobile[;\w ]*|tablet|tv|[^\)]*(?:viera|lg(?:l25|-d300)|alcatel ?o.+|y300-f1)); rv:([\w\.]+)\).+gecko\//i // Firefox OS
], [VERSION, [NAME, FIREFOX+' OS']], [
/\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i, // WebOS
/webos(?:[ \/]?|\.tv-20(?=2[2-9]))(\d[\d\.]*)/i
@@ -1015,7 +1022,6 @@
], [[NAME, "Chrome OS"], VERSION],[
// Smart TVs
/panasonic;(viera)/i, // Panasonic Viera
/(netrange)mmh/i, // Netrange
/(nettv)\/(\d+\.[\w\.]+)/i, // NetTV
@@ -1052,27 +1058,27 @@
var defaultProps = (function () {
var props = { init : {}, isIgnore : {}, isIgnoreRgx : {}, toString : {}};
setProps.call(props.init, [
[UA_BROWSER, [NAME, VERSION, MAJOR, TYPE]],
[UA_CPU, [ARCHITECTURE]],
[UA_DEVICE, [TYPE, MODEL, VENDOR]],
[UA_ENGINE, [NAME, VERSION]],
[UA_OS, [NAME, VERSION]]
[BROWSER, [NAME, VERSION, MAJOR, TYPE]],
[CPU, [ARCHITECTURE]],
[DEVICE, [TYPE, MODEL, VENDOR]],
[ENGINE, [NAME, VERSION]],
[OS, [NAME, VERSION]]
]);
setProps.call(props.isIgnore, [
[UA_BROWSER, [VERSION, MAJOR]],
[UA_ENGINE, [VERSION]],
[UA_OS, [VERSION]]
[BROWSER, [VERSION, MAJOR]],
[ENGINE, [VERSION]],
[OS, [VERSION]]
]);
setProps.call(props.isIgnoreRgx, [
[UA_BROWSER, / ?browser$/i],
[UA_OS, / ?os$/i]
[BROWSER, / ?browser$/i],
[OS, / ?os$/i]
]);
setProps.call(props.toString, [
[UA_BROWSER, [NAME, VERSION]],
[UA_CPU, [ARCHITECTURE]],
[UA_DEVICE, [VENDOR, MODEL]],
[UA_ENGINE, [NAME, VERSION]],
[UA_OS, [NAME, VERSION]]
[BROWSER, [NAME, VERSION]],
[CPU, [ARCHITECTURE]],
[DEVICE, [VENDOR, MODEL]],
[ENGINE, [NAME, VERSION]],
[OS, [NAME, VERSION]]
]);
return props;
})();
@@ -1116,14 +1122,14 @@
return item.detectFeature().get();
};
if (itemType != UA_RESULT) {
if (itemType != RESULT) {
IData.prototype.is = function (strToCheck) {
var is = false;
for (var i in this) {
if (this.hasOwnProperty(i) && !has(is_ignoreProps, i) && lowerize(is_ignoreRgx ? strip(is_ignoreRgx, this[i]) : this[i]) == lowerize(is_ignoreRgx ? strip(is_ignoreRgx, strToCheck) : strToCheck)) {
is = true;
if (strToCheck != UNDEF_TYPE) break;
} else if (strToCheck == UNDEF_TYPE && is) {
if (strToCheck != TYPEOF.UNDEFINED) break;
} else if (strToCheck == TYPEOF.UNDEFINED && is) {
is = !is;
break;
}
@@ -1133,33 +1139,33 @@
IData.prototype.toString = function () {
var str = EMPTY;
for (var i in toString_props) {
if (typeof(this[toString_props[i]]) !== UNDEF_TYPE) {
if (typeof(this[toString_props[i]]) !== TYPEOF.UNDEFINED) {
str += (str ? ' ' : EMPTY) + this[toString_props[i]];
}
}
return str || UNDEF_TYPE;
return str || TYPEOF.UNDEFINED;
};
}
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();
};
@@ -1173,196 +1179,24 @@
setProps.call(this, CH_ALL_VALUES);
if (isHttpUACH) {
setProps.call(this, [
[BRANDS, itemListToArray(uach[CH_HEADER])],
[FULLVERLIST, itemListToArray(uach[CH_HEADER_FULL_VER_LIST])],
[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])],
[FORMFACTORS, itemListToArray(uach[CH_HEADER_FORM_FACTORS])],
[BITNESS, stripQuotes(uach[CH_HEADER_BITNESS])]
[BRANDS, itemListToArray(uach[CH])],
[FULLVERLIST, itemListToArray(uach[CH_FULL_VER_LIST])],
[MOBILE, /\?1/.test(uach[CH_MOBILE])],
[MODEL, stripQuotes(uach[CH_MODEL])],
[PLATFORM, stripQuotes(uach[CH_PLATFORM])],
[PLATFORMVER, stripQuotes(uach[CH_PLATFORM_VER])],
[ARCHITECTURE, stripQuotes(uach[CH_ARCH])],
[FORMFACTORS, itemListToArray(uach[CH_FORM_FACTORS])],
[BITNESS, stripQuotes(uach[CH_BITNESS])]
]);
} else {
for (var prop in uach) {
if(this.hasOwnProperty(prop) && typeof uach[prop] !== UNDEF_TYPE) this[prop] = uach[prop];
if(this.hasOwnProperty(prop) && typeof uach[prop] !== TYPEOF.UNDEFINED) this[prop] = uach[prop];
}
}
}
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 UA_BROWSER:
// Brave-specific detection
if (NAVIGATOR.brave && typeof NAVIGATOR.brave.isBrave == FUNC_TYPE) {
this.set(NAME, 'Brave');
}
break;
case UA_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 !== UNDEF_TYPE && NAVIGATOR.maxTouchPoints && NAVIGATOR.maxTouchPoints > 2) {
this.set(MODEL, 'iPad')
.set(TYPE, TABLET);
}
break;
case UA_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 UA_RESULT:
var data = this.data;
var detect = function (itemType) {
return data[itemType]
.getItem()
.detectFeature()
.get();
};
this.set(UA_BROWSER, detect(UA_BROWSER))
.set(UA_CPU, detect(UA_CPU))
.set(UA_DEVICE, detect(UA_DEVICE))
.set(UA_ENGINE, detect(UA_ENGINE))
.set(UA_OS, detect(UA_OS));
}
}
return this;
};
this.parseUA = function () {
if (this.itemType != UA_RESULT) {
rgxMapper.call(this.data, this.ua, this.rgxMap);
}
if (this.itemType == UA_BROWSER) {
this.set(MAJOR, majorize(this.get(VERSION)));
}
return this;
};
this.parseCH = function () {
var uaCH = this.uaCH,
rgxMap = this.rgxMap;
switch (this.itemType) {
case UA_BROWSER:
case UA_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 == UA_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 == UA_ENGINE && brandName == CHROMIUM) {
this.set(VERSION, brandVersion);
}
}
}
break;
case UA_CPU:
var archName = uaCH[ARCHITECTURE];
if (archName) {
if (archName && uaCH[BITNESS] == '64') archName += '64';
rgxMapper.call(this.data, archName + ';', rgxMap);
}
break;
case UA_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 UA_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 UA_RESULT:
var data = this.data;
var parse = function (itemType) {
return data[itemType]
.getItem()
.setCH(uaCH)
.parseCH()
.get();
};
this.set(UA_BROWSER, parse(UA_BROWSER))
.set(UA_CPU, parse(UA_CPU))
.set(UA_DEVICE, parse(UA_DEVICE))
.set(UA_ENGINE, parse(UA_ENGINE))
.set(UA_OS, parse(UA_OS));
}
return this;
};
setProps.call(this, [
['itemType', itemType],
['ua', ua],
@@ -1370,15 +1204,196 @@
['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 === OBJ_TYPE) {
if (typeof ua === TYPEOF.OBJECT) {
if (isExtensions(ua, true)) {
if (typeof extensions === OBJ_TYPE) {
if (typeof extensions === TYPEOF.OBJECT) {
headers = extensions; // case UAParser(extensions, headers)
}
extensions = ua; // case UAParser(extensions)
@@ -1387,13 +1402,13 @@
extensions = undefined;
}
ua = undefined;
} else if (typeof ua === STR_TYPE && !isExtensions(extensions, true)) {
} else if (typeof ua === TYPEOF.STRING && !isExtensions(extensions, true)) {
headers = extensions; // case UAParser(ua, headers)
extensions = undefined;
}
if (headers) {
if (typeof headers.append === FUNC_TYPE) {
if (typeof headers.append === TYPEOF.FUNCTION) {
// Convert Headers object into a plain object
var kv = {};
headers.forEach(function (v, k) { kv[String(k).toLowerCase()] = v; });
@@ -1414,7 +1429,7 @@
return new UAParser(ua, extensions, headers).getResult();
}
var userAgent = typeof ua === STR_TYPE ? ua : // Passed user-agent string
var userAgent = typeof ua === TYPEOF.STRING ? ua : // Passed user-agent string
(headers && headers[USER_AGENT] ? headers[USER_AGENT] : // User-Agent from passed headers
((NAVIGATOR && NAVIGATOR.userAgent) ? NAVIGATOR.userAgent : // navigator.userAgent
EMPTY)), // empty string
@@ -1425,15 +1440,15 @@
defaultRegexes,
createItemFunc = function (itemType) {
if (itemType == UA_RESULT) {
if (itemType == RESULT) {
return function () {
return new UAItem(itemType, userAgent, regexMap, httpUACH)
.set('ua', userAgent)
.set(UA_BROWSER, this.getBrowser())
.set(UA_CPU, this.getCPU())
.set(UA_DEVICE, this.getDevice())
.set(UA_ENGINE, this.getEngine())
.set(UA_OS, this.getOS())
.set(BROWSER, this.getBrowser())
.set(CPU, this.getCPU())
.set(DEVICE, this.getDevice())
.set(ENGINE, this.getEngine())
.set(OS, this.getOS())
.get();
};
} else {
@@ -1447,16 +1462,15 @@
// public methods
setProps.call(this, [
['getBrowser', createItemFunc(UA_BROWSER)],
['getCPU', createItemFunc(UA_CPU)],
['getDevice', createItemFunc(UA_DEVICE)],
['getEngine', createItemFunc(UA_ENGINE)],
['getOS', createItemFunc(UA_OS)],
['getResult', createItemFunc(UA_RESULT)],
['getBrowser', createItemFunc(BROWSER)],
['getCPU', createItemFunc(CPU)],
['getDevice', createItemFunc(DEVICE)],
['getEngine', createItemFunc(ENGINE)],
['getOS', createItemFunc(OS)],
['getResult', createItemFunc(RESULT)],
['getUA', function () { return userAgent; }],
['setUA', function (ua) {
if (isString(ua))
userAgent = ua.length > UA_MAX_LENGTH ? trim(ua, UA_MAX_LENGTH) : ua;
if (isString(ua)) userAgent = trim(ua, UA_MAX_LENGTH);
return this;
}]
])

View File

@@ -171,6 +171,16 @@
"major" : "0"
}
},
{
"desc" : "Atlas",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Atlas/1.0",
"expect" :
{
"name" : "Atlas",
"version" : "1.0",
"major" : "1"
}
},
{
"desc" : "Avant",
"ua" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB5; Avant Browser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
@@ -271,6 +281,26 @@
"major" : "11"
}
},
{
"desc" : "Bing",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/605.1.15 BingSapphire/31.8.430522001",
"expect" :
{
"name" : "Bing",
"version" : "31.8.430522001",
"major" : "31"
}
},
{
"desc" : "Bing",
"ua" : "Mozilla/5.0 (Linux; Android 9; MIX 2 Build/PKQ1.190118.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4893.0 Mobile Safari/537.36 BingWeb/6.9.12",
"expect" :
{
"name" : "Bing",
"version" : "6.9.12",
"major" : "6"
}
},
{
"desc" : "Blazer",
"ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; PalmSource/hspr-H102; Blazer/4.0) 16;320x320",
@@ -611,6 +641,16 @@
"major" : "55"
}
},
{
"desc" : "PaleMoon",
"ua" : "(Windows NT 6.2; WOW64) KHTML/4.11 Gecko/20130308 Firefox/23.0 (PaleMoon/20.3)",
"expect" :
{
"name" : "PaleMoon",
"version" : "20.3",
"major" : "20"
}
},
{
"desc" : "PaleMoon",
"ua" : "Mozilla/5.0 (X11; Linux x86_64; rv:52.9) Gecko/20100101 Goanna/3.4 Firefox/52.9 PaleMoon/27.6.1",
@@ -826,6 +866,16 @@
"major" : "40"
}
},
{
"desc" : "Hi Browser",
"ua" : "Mozilla/5.0 (Linux; Android 14; en; TECNO BG6m Build/SP1A.210812.016) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 HiBrowser/v2.25.6.3;lang=es;nation=DO;locale=es_DO UWS/ Mobile Safari/537.36",
"expect" :
{
"name" : "HiBrowser",
"version" : "2.25.6.3",
"major" : "2"
}
},
{
"desc" : "HuaweiBrowser",
"ua" : "Mozilla/5.0 (Linux; Android 6.0.1; LYA-AL00HMSCore/4.0.0 GMS/10.4 ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.64 HuaweiBrowser/10.0.3.102 Mobile Safari/537.36",
@@ -1338,6 +1388,16 @@
"major" : "5"
}
},
{
"desc" : "Mozilla",
"ua" : "Mozilla/2.02 [fr] (WinNT; I)",
"expect" :
{
"name" : "Mozilla",
"version" : "2.02",
"major" : "2"
}
},
{
"desc" : "MSIE",
"ua" : "Mozilla/4.0 (compatible; MSIE 5.0b1; Mac_PowerPC)",
@@ -1558,6 +1618,16 @@
"major" : "60"
}
},
{
"desc" : "Opera Neon",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.21 Safari/537.36 MMS/1.0.2531.0",
"expect" :
{
"name" : "Opera Neon",
"version" : "1.0.2531.0",
"major" : "1"
}
},
{
"desc" : "Opera Tablet",
"ua" : "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1",
@@ -1688,6 +1758,36 @@
"major" : "2"
}
},
{
"desc" : "Qwant",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) QwantMobile/6.7.6 Mobile/15E148 Safari/605.1.15",
"expect" :
{
"name" : "Qwant",
"version" : "6.7.6",
"major" : "6"
}
},
{
"desc" : "Qwant",
"ua" : "QwantMobile/2.0 (Android 8.0.0; Mobile; rv:59.0) Gecko/59.0 Firefox/59.0 QwantBrowser/59.0",
"expect" :
{
"name" : "Qwant",
"version" : "59.0",
"major" : "59"
}
},
{
"desc" : "Qwant",
"ua" : "QwantMobile/2.0 (iPad; CPU OS 15_8_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) QwantiOS/2.7.0b1 Mobile/15E148 Safari/605.1.15",
"expect" :
{
"name" : "Qwant",
"version" : "2.0",
"major" : "2"
}
},
{
"desc" : "Rekonq 2",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.21 (KHTML, like Gecko) rekonq/2.2.1 Safari/537.21",
@@ -1879,6 +1979,36 @@
"major" : "20"
}
},
{
"desc" : "Steam Client",
"ua" : "Mozilla/5.0 (X11; Linux x86_64; Valve Steam Client/default/1705108172) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
"expect" :
{
"name" : "Steam",
"version" : "undefined",
"major" : "undefined"
}
},
{
"desc" : "Steam Big Picture",
"ua" : "Mozilla/5.0 (Linux; U; X11; en-US; Valve Steam Tenfoot/1660688177; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36",
"expect" :
{
"name" : "Steam",
"version" : "undefined",
"major" : "undefined"
}
},
{
"desc" : "Steam Overlay",
"ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Valve Steam GameOverlay/default/1741737356) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.183 Safari/537.36",
"expect" :
{
"name" : "Steam",
"version" : "undefined",
"major" : "undefined"
}
},
{
"desc" : "Swiftfox",
"ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 (Swiftfox)",
@@ -2388,6 +2518,26 @@
"major" : "1"
}
},
{
"desc" : "Brave",
"ua" : "Mozilla/5.0 (Linux; Android 11; SM-T870) AppleWebKit/537.36 (KHTML, like Gecko) Brave Chrome/88.0.4324.96 Safari/537.36",
"expect" :
{
"name" : "Brave",
"version" : "88.0.4324.96",
"major" : "88"
}
},
{
"desc" : "Brave",
"ua" : "Mozilla/5.0 (Linux; Android 11; Samsung Galaxy F62 SM-E625F Build/SME625F512091028;) AppleWebKit/537.36 (KHTML, like Gecko) Brave/1.75.110 Mobile Safari/537.36",
"expect" :
{
"name" : "Brave",
"version" : "1.75.110",
"major" : "1"
}
},
{
"desc" : "Brave Browser",
"ua" : "Brave/4.5.16 CFNetwork/893.13.1 Darwin/17.3.0 (x86_64)",
@@ -2632,6 +2782,16 @@
"major" : "28",
"type" : "inapp"
}
},
{
"desc" : "TikTok",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1 trill_43.0.0 BytedanceWebview/d8a21c6",
"expect" : {
"name" : "TikTok",
"version": "43.0.0",
"major" : "43",
"type" : "inapp"
}
},
{
"desc" : "Chrome Mobile",

View File

@@ -289,7 +289,15 @@
},
{
"desc" : "68k",
"ua" : "'Mozilla/1.1 (Macintosh; U; 68K)'",
"ua" : "Mozilla/1.1 (Macintosh; U; 68K)",
"expect" :
{
"architecture" : "68k"
}
},
{
"desc" : "MC680x0",
"ua" : "AmigaVoyager/3.2 (AmigaOS/MC680x0)",
"expect" :
{
"architecture" : "68k"

View File

@@ -0,0 +1,11 @@
[
{
"desc": "Anbernic",
"ua": "Mozilla/5.0 (Linux; Android 13; RGCUBE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
"expect": {
"vendor": "Anbernic",
"model": "RGCUBE",
"type": "console"
}
}
]

View File

@@ -44,6 +44,105 @@
"type": "tablet"
}
},
{
"desc": "iPad using Chrome",
"ua": "Mozilla/5.0 (iPad13,10; CPU OS 16_6_2 like Mac OS X) AppleWebKit/602.3 (KHTML, like Gecko) CriOS/97.0.0.5927.72 Mobile/11S296 Safari/602.3",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using DuckDuckGo",
"ua": "Mozilla/5.0 (iPad; iPad13,6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) DuckDuckGo/130.0.0.0 Mobile/15E148 Safari/604.1",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using Discord",
"ua": "Discord/52.0 (iPad; iOS 14.4; Scale/2.00)",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using Firefox",
"ua": "Mozilla/5.0 (iPad; iPad14,2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Firefox/120.0 Mobile/15E148 Safari/604.1",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using iTunes",
"ua": "itunesstored/1.0 iOS/15.6.1 model/iPad13,8 hwp/t8103 build/19G82 (5; dt:238)",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using MS OneNote",
"ua": "Microsoft Office OneNote/16.81/240108 (iOS/16.5.1; Tablet; AppStore; Apple/iPad8,3)",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using MS Word",
"ua": "Microsoft Office Word/2.44.1211 (iOS/13.7; Tablet; es-MX; AppStore; Apple/iPad11,3)",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using Quora",
"ua": "Quora 8.4.30 rv:3230 env:prod (iPad11,3; iPadOS 17.7; en_GB) AppleWebKit",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using TuneIn Radio",
"ua": "TuneIn Radio/27.1.0; iPad6,3; iPadOS/16.6",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using TuneIn Radio Pro",
"ua": "TuneIn Radio Pro/21.4.1; iPad8,9; iOS/15.0",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPad using YouTube",
"ua": "com.google.ios.youtube/20.31.6 (iPad13,5; U; CPU iPadOS 18_6 like Mac OS X; en_US)",
"expect": {
"vendor": "Apple",
"model": "iPad",
"type": "tablet"
}
},
{
"desc": "iPod",
"ua": "Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_4 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B554a Safari/9537.53",
@@ -62,6 +161,15 @@
"type": "mobile"
}
},
{
"desc": "iPhone using Spotify",
"ua": "Spotify/8.7.70 iOS/16.0 (iPhone15,3)",
"expect": {
"vendor": "Apple",
"model": "iPhone15,3",
"type": "mobile"
}
},
{
"desc": "iPhone SE",
"ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPhone8,4;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]",

View File

@@ -1,7 +1,25 @@
[
{
"desc": "BlackBerry 9650",
"ua": "Mozilla/5.0 (BlackBerry; U; BlackBerry 9650; en-US) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.524 Mobile Safari/534.8+",
"expect": {
"vendor": "BlackBerry",
"model": "9650",
"type": "mobile"
}
},
{
"desc": "BlackBerry 9780",
"ua": "Mozilla/5.0 (BlackBerry; U; BlackBerry 9780; en) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.546 Mobile Safari/534.8+",
"expect": {
"vendor": "BlackBerry",
"model": "9780",
"type": "mobile"
}
},
{
"desc": "BlackBerry Priv",
"ua": "User-Agent: Mozilla/5.0 (Linux; Android 5.1.1; STV100-1 Build/LMY47V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36",
"ua": "Mozilla/5.0 (Linux; Android 5.1.1; STV100-1 Build/LMY47V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/46.0.2490.76 Mobile Safari/537.36",
"expect": {
"vendor": "BlackBerry",
"model": "STV100-1",
@@ -28,7 +46,7 @@
},
{
"desc": "BlackBerry Key2 LE",
"ua": "User-Agent: Mozilla/5.0 (Linux; Android 8.1.0; BBE100-1 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497 Mobile Safari/537.36",
"ua": "Mozilla/5.0 (Linux; Android 8.1.0; BBE100-1 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497 Mobile Safari/537.36",
"expect": {
"vendor": "BlackBerry",
"model": "BBE100-1",

View File

@@ -0,0 +1,20 @@
[
{
"desc": "Hisense E50 Lite",
"ua": "Mozilla/5.0 (Linux; Android 11; Hisense E50 Lite) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.126 Mobile Safari/537.36",
"expect": {
"vendor": "Hisense",
"model": "E50 Lite",
"type": "mobile"
}
},
{
"desc": "Hisense V40s",
"ua": "Mozilla/5.0 (Linux; Android 11; Hisense V40s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.125 Mobile Safari/537.36",
"expect": {
"vendor": "Hisense",
"model": "V40s",
"type": "mobile"
}
}
]

View File

@@ -701,6 +701,15 @@
"type": "mobile"
}
},
{
"desc": "Huawei P10 Lite",
"ua": "Mozilla/5.0 (Linux; Android 8.0.0; WAS-L03T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5199.205 Mobile Safari/537.36",
"expect": {
"vendor": "Huawei",
"model": "WAS-L03T",
"type": "mobile"
}
},
{
"desc": "Huawei P20 Lite",
"ua": "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36",
@@ -728,6 +737,15 @@
"type": "mobile"
}
},
{
"desc": "Huawei P30",
"ua": "Mozilla/5.0 (Linux; Android 10; ELE-L04) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.109 Mobile Safari/537.36",
"expect": {
"vendor": "Huawei",
"model": "ELE-L04",
"type": "mobile"
}
},
{
"desc": "Huawei P30",
"ua": "Mozilla/5.0 (Linux; Android 9; ELE-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36",
@@ -782,6 +800,15 @@
"type": "mobile"
}
},
{
"desc": "Huawei Nova",
"ua": "Mozilla/5.0 (Linux; Android 7.0; HUAWEI CAN-L13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5113.212 Mobile Safari/537.36",
"expect": {
"vendor": "Huawei",
"model": "CAN-L13",
"type": "mobile"
}
},
{
"desc": "Huawei Nova 5T",
"ua": "Mozilla/5.0 (Linux; Android 10; YAL-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
@@ -899,6 +926,15 @@
"type": "mobile"
}
},
{
"desc": "Huawei Y7p",
"ua": "Mozilla/5.0 (Linux; Android 10; ART-L28; HMSCore 6.8.0.311) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4476.0 HuaweiBrowser/12.1.2.312 Mobile Safari/537.36",
"expect": {
"vendor": "Huawei",
"model": "ART-L28",
"type": "mobile"
}
},
{
"desc": "Huawei Y7p",
"ua": "Mozilla/5.0 (Linux; Android 9; ART-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36",

View File

@@ -188,6 +188,15 @@
"type": "tablet"
}
},
{
"desc": "LG G Pad F2",
"ua": "Mozilla/5.0 (Linux; Android 7.1.1; LG-LK460 Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Safari/537.36",
"expect": {
"vendor": "LG",
"model": "LK460",
"type": "tablet"
}
},
{
"desc": "LG Watch Urbane",
"ua": "Mozilla/5.0 Linux; Android 7.1.1; LG Watch Urbane Build/NWD1.180306.004 AppleWebKit/537.36 KHTML, like Gecko Chrome/19.77.34.5 Mobile Safari/537.36",

View File

@@ -0,0 +1,11 @@
[
{
"desc": "Logitech",
"ua": "Dalvik/2.1.0 (Linux; U; Android 11; GR0006 Build/SQ02K.00.0018)",
"expect": {
"vendor": "Logitech",
"model": "GR0006",
"type": "console"
}
}
]

View File

@@ -1,4 +1,13 @@
[
{
"desc": "Nokia 1",
"ua": "Mozilla/5.0 (Linux; Android 10; Nokia 1 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.15 Mobile Safari/537.36",
"expect": {
"vendor": "Nokia",
"model": "1",
"type": "mobile"
}
},
{
"desc": "Nokia3xx",
"ua": "Nokia303/14.87 CLDC-1.1",

View File

@@ -170,6 +170,15 @@
"type": "mobile"
}
},
{
"desc": "OnePlus 15",
"ua": "Mozilla/5.0 (Linux; Android 16; PLK110 Build/BP2A.250605.015; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.135 Mobile Safari/537.36",
"expect": {
"vendor": "OnePlus",
"model": "PLK110",
"type": "mobile"
}
},
{
"desc": "OnePlus Ace",
"ua": "Mozilla/5.0 (Linux; U; Android 15; zh-cn; PGKM10 Build/AP3A.240617.008) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.168 Mobile Safari/537.36 HeyTapBrowser/40.9.6.2",

View File

@@ -0,0 +1,11 @@
[
{
"desc": "Valve",
"ua": "Mozilla/5.0 (X11; Linux x86_64; Valve Steam Client/Steam Deck [Steam Deck Beta]/default/0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.183 Safari/537.36",
"expect": {
"vendor": "Valve",
"model": "Steam Deck",
"type": "console"
}
}
]

View File

@@ -0,0 +1,29 @@
[
{
"desc": "Wiko Life 3",
"ua": "Mozilla/5.0 (Linux; Android 11; Wiko U316AT) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5199.205 Mobile Safari/537.36",
"expect": {
"vendor": "Wiko",
"model": "U316AT",
"type": "mobile"
}
},
{
"desc": "Wiko Ride 3",
"ua": "Mozilla/5.0 (Linux; Android 11; Wiko U614AS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.181 Mobile Safari/537.36",
"expect": {
"vendor": "Wiko",
"model": "U614AS",
"type": "mobile"
}
},
{
"desc": "Wiko T10",
"ua": "Mozilla/5.0 (Linux; Android 11; WIKO T10 Build/RP1A.200720.011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.62 Mobile Safari/537.36",
"expect": {
"vendor": "WIKO",
"model": "T10",
"type": "mobile"
}
}
]

View File

@@ -71,6 +71,51 @@
"type": "mobile"
}
},
{
"desc": "XiaoMi Black Shark 3",
"ua": "Mozilla/5.0 (Linux; U; Android 11; zh-CN; SHARK KLE-A0 Build/KLEN2211210CN00MR6) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/17.3.8.1369 Mobile Safari/537.36",
"expect": {
"vendor": "Xiaomi",
"model": "SHARK KLE-A0",
"type": "mobile"
}
},
{
"desc": "XiaoMi Black Shark 4",
"ua": "Mozilla/5.0 (Linux; U; Android 11; en-US; SHARK PRS-H0 Build/PROS2203060OS00MP5) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.4.0.1306 Mobile Safari/537.36",
"expect": {
"vendor": "Xiaomi",
"model": "SHARK PRS-H0",
"type": "mobile"
}
},
{
"desc": "XiaoMi Black Shark 4 Pro",
"ua": "Mozilla/5.0 (Linux; U; Android 11; es-us; SHARK KSR-H0 Build/KASE2208050OS00MP4) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Mobile Safari/537.36 XiaoMi/MiuiBrowser/13.22.1-gn",
"expect": {
"vendor": "Xiaomi",
"model": "SHARK KSR-H0",
"type": "mobile"
}
},
{
"desc": "XiaoMi Black Shark 5",
"ua": "Mozilla/5.0 (Linux; arm_64; Android 12; SHARK PAR-H0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 YaBrowser/23.5.0.256.00 SA/3 Mobile Safari/537.36",
"expect": {
"vendor": "Xiaomi",
"model": "SHARK PAR-H0",
"type": "mobile"
}
},
{
"desc": "XiaoMi Civi 5 Pro",
"ua": "Mozilla/5.0 (Linux; U; Android 15; zh-CN; 25067PYE3C Build/AQ3A.250129.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/18.0.4.1430 Mobile Safari/537.36",
"expect": {
"vendor": "Xiaomi",
"model": "25067PYE3C",
"type": "mobile"
}
},
{
"desc": "Xiaomi Hongmi Note 1W",
"ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-CN; HM NOTE 1W Build/JDQ39) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/9.7.9.439 U3/0.8.0 Mobile Safari/533.1",
@@ -116,6 +161,15 @@
"type": "mobile"
}
},
{
"desc": "Xiaomi Mi 11 Lite 5G",
"ua": "Mozilla/5.0 (Linux; U; Android 12; zh-CN; Mi 11 Lite 5G Build/SKQ1.211006.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.136 Mobile Safari/537.36 XiaoMi/MiuiBrowser/13.17.0-gn",
"expect": {
"vendor": "Xiaomi",
"model": "Mi 11 Lite 5G",
"type": "mobile"
}
},
{
"desc": "Xiaomi Mi 5s Plus",
"ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; MI 5s Plus Build/MXB48T) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/8.7.1",
@@ -188,6 +242,15 @@
"type": "mobile"
}
},
{
"desc": "Xiaomi Mi 10T",
"ua": "Mozilla/5.0 (Linux; U; Android 12; fr-CA; Mi 10T Build/SKQ1.211006.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.79 Mobile Safari/537.36 XiaoMi/MiuiBrowser/13.16.1-gn",
"expect": {
"vendor": "Xiaomi",
"model": "Mi 10T",
"type": "mobile"
}
},
{
"desc": "Xiaomi Mi A2",
"ua": "Mozilla/5.0 (Linux; Android 9; Mi A2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36",
@@ -314,6 +377,15 @@
"type": "tablet"
}
},
{
"desc": "Xiaomi Pad 5",
"ua": "Mozilla/5.0 (Linux; U; Android 12; ca-ES; Xiaomi Pad 5 Build/SKQ1.220303.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.86 Mobile Safari/537.36 XiaoMi/MiuiBrowser/13.6.0-gn",
"expect": {
"vendor": "Xiaomi",
"model": "mi Pad 5",
"type": "tablet"
}
},
{
"desc": "Xiaomi Pad 6S Pro 12.4",
"ua": "Mozilla/5.0 (Linux; Android 14; 24018RPACC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
@@ -386,6 +458,42 @@
"type": "mobile"
}
},
{
"desc": "Xiaomi Qin 1s+",
"ua": "Mozilla/5.0 (Linux; U; Android 4.4.4; Qin 1s+ Build/KTU84P; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36 OPR/46.0.2254.145391",
"expect": {
"vendor": "Xiaomi",
"model": "Qin 1s+",
"type": "mobile"
}
},
{
"desc": "Xiaomi Qin 2",
"ua": "Mozilla/5.0 (Linux; Android 9; Qin 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36",
"expect": {
"vendor": "Xiaomi",
"model": "Qin 2",
"type": "mobile"
}
},
{
"desc": "Xiaomi Qin 2 Pro",
"ua": "Mozilla/5.0 (Linux; Android 9.0; QIN2 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4215.0 Mobile Safari/537.36 EdgA/86.0.597.0",
"expect": {
"vendor": "Xiaomi",
"model": "QIN2 Pro",
"type": "mobile"
}
},
{
"desc": "Xiaomi Qin 3 Ultra",
"ua": "Mozilla/5.0 (Linux; Android 12; QIN3ULTRA Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/128.0.6613.120 Mobile Safari/537.36",
"expect": {
"vendor": "Xiaomi",
"model": "QIN3ULTRA",
"type": "mobile"
}
},
{
"desc": "Xiaomi Redmi 4A",
"ua": "Mozilla/5.0 (Linux; Android 6.0; Redmi 4A Build/MMB29M; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36",
@@ -404,6 +512,15 @@
"type": "mobile"
}
},
{
"desc": "Xiaomi Redmi 15",
"ua": "Mozilla/5.0 (Linux; Android 15; 25062RN2DA Build/AQ3A.250226.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.212 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/541.0.0.49.79;IABMV/1;]",
"expect": {
"vendor": "Xiaomi",
"model": "25062RN2DA",
"type": "mobile"
}
},
{
"desc": "Xiaomi Redmi K30 5G",
"ua": "Mozilla/5.0 (Linux; Android 10; Redmi K30 5G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36",
@@ -458,6 +575,15 @@
"type": "mobile"
}
},
{
"desc": "XiaoMi Redmi Note 10 Lite",
"ua": "Mozilla/5.0 (Linux; U; Android 12; es-VE; Mi Note 10 Lite Build/SKQ1.210908.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.88 Mobile Safari/537.36 XiaoMi/MiuiBrowser/13.16.1-gn",
"expect": {
"vendor": "Xiaomi",
"model": "Mi Note 10 Lite",
"type": "mobile"
}
},
{
"desc": "XiaoMi Redmi Note 10 Pro",
"ua": "Mozilla/5.0 (Linux; Android 13; M2101K6P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36",
@@ -565,5 +691,14 @@
"model": "MIBOX3",
"type": "smarttv"
}
},
{
"desc": "Xiaomi Watch 2 Pro",
"ua": "Dalvik/2.1.0 (Linux; U; Android 13; Xiaomi Watch 2 Pro Build/TWR7.231031.001.XM064S)",
"expect": {
"vendor": "Xiaomi",
"model": "Watch 2 Pro",
"type": "wearable"
}
}
]

View File

@@ -17,6 +17,15 @@
"version" : "57.0.2987.146"
}
},
{
"desc" : "Dillo",
"ua" : "Dillo/3.1.0",
"expect" :
{
"name" : "Dillo",
"version" : "3.1.0"
}
},
{
"desc" : "EdgeHTML",
"ua" : "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36 Edge/12.0",

View File

@@ -59,6 +59,26 @@
"type" : "cli"
}
},
{
"desc" : "PowerShell",
"ua" : "Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.15063; en-US) PowerShell/6.0.0",
"expect" :
{
"name" : "PowerShell",
"version" : "6.0.0",
"type" : "cli"
}
},
{
"desc" : "PowerShell",
"ua" : "Mozilla/5.0 (Windows NT; Windows NT 10.0; de-DE) WindowsPowerShell/5.1.19041.5737",
"expect" :
{
"name" : "PowerShell",
"version" : "5.1.19041.5737",
"type" : "cli"
}
},
{
"desc" : "wget",
"ua" : "Wget/1.21.1",

View File

@@ -965,7 +965,7 @@
"ua" : "Mozilla/5.0 (compatible; Qwantbot-news/2.0; +https://help.qwant.com/bot/)",
"expect" :
{
"name" : "Qwantbot",
"name" : "Qwantbot-news",
"version" : "2.0",
"type" : "crawler"
}
@@ -1100,6 +1100,26 @@
"type" : "crawler"
}
},
{
"desc" : "SurdotlyBot",
"ua" : "Mozilla/5.0 (compatible; SurdotlyBot/1.0; +http://sur.ly/bot.html)",
"expect" :
{
"name" : "SurdotlyBot",
"version" : "1.0",
"type" : "crawler"
}
},
{
"desc" : "Swiftbot",
"ua" : "Swiftbot/1.0 (swiftype.com)",
"expect" :
{
"name" : "Swiftbot",
"version" : "1.0",
"type" : "crawler"
}
},
{
"desc" : "Teoma",
"ua" : "Mozilla/2.0 (compatible; Ask Jeeves/Teoma; +http://sp.ask.com/docs/about/tech_crawling.html)",

View File

@@ -1,232 +1,587 @@
[
{
"desc" : "Airmail",
"ua" : "Airmail 1.0 rv:148 (Macintosh; Mac OS X 10.8.3; en_BE)",
"expect" :
{
"name" : "Airmail",
"version" : "1.0",
"type" : "email"
"desc": "Airmail",
"ua": "Airmail 1.0 rv:148 (Macintosh; Mac OS X 10.8.3; en_BE)",
"expect": {
"name": "Airmail",
"version": "1.0",
"type": "email"
}
},
{
"desc" : "Apple Mail",
"ua" : "Mail/3826.500.181.1.5 CFNetwork/3826.500.111.1.1 Darwin/24.4.0",
"expect" :
{
"name" : "Mail",
"version" : "3826.500.181.1.5",
"type" : "email"
"desc": "Alpine",
"ua": "Alpine/2.21 (Linux; x86_64)",
"expect": {
"name": "Alpine",
"version": "2.21",
"type": "email"
}
},
{
"desc" : "BlueMail",
"ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) BlueMail/0.10.31 Chrome/61.0.3163.100 Electron/2.0.18 Safari/537.36",
"expect" :
{
"name" : "BlueMail",
"version" : "0.10.31",
"type" : "email"
"desc": "Android Mail (AOSP)",
"ua": "Android/9-email",
"expect": {
"name": "Android",
"version": "9-email",
"type": "email"
}
},
{
"desc" : "BlueMail",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A405 BlueMail iOS",
"expect" :
{
"name" : "BlueMail",
"version" : "iOS",
"type" : "email"
"desc": "Apple Mail",
"ua": "Mail/3826.500.181.1.5 CFNetwork/3826.500.111.1.1 Darwin/24.4.0",
"expect": {
"name": "Mail",
"version": "3826.500.181.1.5",
"type": "email"
}
},
{
"desc" : "DaumMail",
"ua" : "DaumMobileApp (LG-KU5400; U; Android 2.3.7|10; ko-kr) DaumMail/1.0.8 ",
"expect" :
{
"name" : "DaumMail",
"version" : "1.0.8",
"type" : "email"
"desc": "Aqua Mail",
"ua": "AquaMail/1.16.0-1081 (build: 101600003)",
"expect": {
"name": "AquaMail",
"version": "1.16.0-1081",
"type": "email"
}
},
{
"desc" : "Evolution",
"ua" : "Evolution/3.52.3",
"expect" :
{
"name" : "Evolution",
"version" : "3.52.3",
"type" : "email"
"desc": "Balsa",
"ua": "Balsa/2.6.4",
"expect": {
"name": "Balsa",
"version": "2.6.4",
"type": "email"
}
},
{
"desc" : "eM Client",
"ua" : "eMClient/9.2.2157.0",
"expect" :
{
"name" : "eMClient",
"version" : "9.2.2157.0",
"type" : "email"
"desc": "Barca",
"ua": "Barca/2.8 (Windows NT 5.1)",
"expect": {
"name": "Barca",
"version": "2.8",
"type": "email"
}
},
{
"desc" : "Foxmail",
"ua" : "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36 foxmail/7.2.15.80",
"expect" :
{
"name" : "foxmail",
"version" : "7.2.15.80",
"type" : "email"
"desc": "BlueMail",
"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) BlueMail/0.10.31 Chrome/61.0.3163.100 Electron/2.0.18 Safari/537.36",
"expect": {
"name": "BlueMail",
"version": "0.10.31",
"type": "email"
}
},
{
"desc" : "KMail",
"ua" : "KMail/4.14.10 (FreeBSD/12.0-CURRENT; KDE/4.14.10; amd64; ; )",
"expect" :
{
"name" : "KMail",
"version" : "4.14.10",
"type" : "email"
"desc": "BlueMail (iOS)",
"ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A405 BlueMail iOS",
"expect": {
"name": "BlueMail",
"version": "iOS",
"type": "email"
}
},
{
"desc" : "KMail2",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) kmail2/5.7.3 Safari/534.34",
"expect" :
{
"name" : "kmail2",
"version" : "5.7.3",
"type" : "email"
"desc": "Canary Mail",
"ua": "Canary/413 (Macintosh; OS X 14.2.1)",
"expect": {
"name": "Canary",
"version": "413",
"type": "email"
}
},
{
"desc" : "Kontact",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) kontact/4.13.3 Safari/534.34",
"expect" :
{
"name" : "kontact",
"version" : "4.13.3",
"type" : "email"
"desc": "Claws Mail",
"ua": "Claws Mail/3.17.5 (x86_64-pc-linux-gnu)",
"expect": {
"name": "Claws Mail",
"version": "3.17.5",
"type": "email"
}
},
{
"desc" : "Microsoft Outlook",
"ua" : "Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.5431; Pro)",
"expect" :
{
"name" : "Microsoft Outlook",
"version" : "16.0.5431",
"type" : "email"
"desc": "DaumMail",
"ua": "DaumMobileApp (LG-KU5400; U; Android 2.3.7|10; ko-kr) DaumMail/1.0.8 ",
"expect": {
"name": "DaumMail",
"version": "1.0.8",
"type": "email"
}
},
{
"desc" : "Microsoft Outlook for Mac",
"ua" : "MacOutlook/14.7.1.161129 (Intel Mac OS X 10.9.6)",
"expect" :
{
"name" : "MacOutlook",
"version" : "14.7.1.161129",
"type" : "email"
"desc": "eM Client",
"ua": "eMClient/9.2.2157.0",
"expect": {
"name": "eMClient",
"version": "9.2.2157.0",
"type": "email"
}
},
{
"desc" : "NaverMailApp",
"ua" : "NaverMailApp/2.1.23 (Android 10; SM-N960N)",
"expect" :
{
"name" : "NaverMailApp",
"version" : "2.1.23",
"type" : "email"
"desc": "Eudora OSE",
"ua": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.19) Gecko/20110420 Eudora/3.0.4",
"expect": {
"name": "Eudora",
"version": "3.0.4",
"type": "email"
}
},
{
"desc" : "Polymail",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Polymail/2.3.12 Chrome/114.0.5735.134 Electron/25.2.0 Safari/537.36",
"expect" :
{
"name" : "Polymail",
"version" : "2.3.12",
"type" : "email"
"desc": "Evolution",
"ua": "Evolution/3.52.3",
"expect": {
"name": "Evolution",
"version": "3.52.3",
"type": "email"
}
},
{
"desc" : "ProtonMail",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ProtonMail/1.4.0 Chrome/122.0.6261.156 Electron/29.4.6 Safari/537.36",
"expect" :
{
"name" : "ProtonMail",
"version" : "1.4.0",
"type" : "email"
"desc": "FairEmail",
"ua": "FairEmail/1.2066 (Android 13; Pixel 7)",
"expect": {
"name": "FairEmail",
"version": "1.2066",
"type": "email"
}
},
{
"desc" : "SparkDesktop",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) SparkDesktop/3.0.30 Chrome/102.0.5005.63 Electron/19.0.4 Safari/537.36",
"expect" :
{
"name" : "SparkDesktop",
"version" : "3.0.30",
"type" : "email"
"desc": "Foxmail",
"ua": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36 foxmail/7.2.15.80",
"expect": {
"name": "foxmail",
"version": "7.2.15.80",
"type": "email"
}
},
{
"desc" : "Sparrow",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Sparrow/1043.1",
"expect" :
{
"name" : "Sparrow",
"version" : "1043.1",
"type" : "email"
"desc": "Geary",
"ua": "Geary/40.0 (Linux; x86_64)",
"expect": {
"name": "Geary",
"version": "40.0",
"type": "email"
}
},
{
"desc" : "Thunderbird",
"ua" : "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0",
"expect" :
{
"name" : "Thunderbird",
"version" : "78.13.0",
"type" : "email"
"desc": "Gnus",
"ua": "Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux)",
"expect": {
"name": "Gnus",
"version": "5.13",
"type": "email"
}
},
{
"desc" : "Yahoo! Mail",
"ua" : "YahooMobile/1.0 (mail; 3.0.5.1311380); (Linux; U; Android 4.0.3; htc_runnymede Build/ICE_CREAM_SANDWICH_MR1);",
"expect" :
{
"name" : "Yahoo",
"version" : "3.0.5.1311380",
"type" : "email"
"desc": "Horde IMP",
"ua": "Horde::IMP/6.2.27 (Linux)",
"expect": {
"name": "Horde::IMP",
"version": "6.2.27",
"type": "email"
}
},
{
"desc" : "Yahoo! Mail",
"ua" : "YahooMobileMail/1.0 (Android Mail; 1.3.10) (supersonic;HTC;PC36100;2.3.5/GRJ90) ",
"expect" :
{
"name" : "Yahoo",
"version" : "1.3.10",
"type" : "email"
"desc": "IBM Notes",
"ua": "Lotus-Notes/8.5.3",
"expect": {
"name": "Lotus-Notes",
"version": "8.5.3",
"type": "email"
}
},
{
"desc" : "Zimbra",
"ua" : "Mozilla/5.0 (Windows; U; Windows NT 6.2; it; rv:1.9.2.19pre) Gecko/20110902 Prism zdesktop/7.2.8",
"expect" :
{
"name" : "Zimbra",
"version" : "7.2.8",
"type" : "email"
"desc": "IncrediMail",
"ua": "IncrediMail/6.29.5126",
"expect": {
"name": "IncrediMail",
"version": "6.29.5126",
"type": "email"
}
},
{
"desc": "K-9 Mail",
"ua": "K-9 Mail/5.600",
"expect": {
"name": "K-9 Mail",
"version": "5.600",
"type": "email"
}
},
{
"desc" : "ZohoMail",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ZohoMail-Desktop/1.6.3 Chrome/124.0.6367.207 Electron/30.0.6 Safari/537.36",
"expect" :
{
"name" : "ZohoMail-Desktop",
"version" : "1.6.3",
"type" : "email"
"desc": "KMail",
"ua": "KMail/4.14.10 (FreeBSD/12.0-CURRENT; KDE/4.14.10; amd64; ; )",
"expect": {
"name": "KMail",
"version": "4.14.10",
"type": "email"
}
},
{
"desc": "KMail2",
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) kmail2/5.7.3 Safari/534.34",
"expect": {
"name": "kmail2",
"version": "5.7.3",
"type": "email"
}
},
{
"desc": "Kontact",
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) kontact/4.13.3 Safari/534.34",
"expect": {
"name": "kontact",
"version": "4.13.3",
"type": "email"
}
},
{
"desc": "Mailbird",
"ua": "Mailbird/2.9.0.0",
"expect": {
"name": "Mailbird",
"version": "2.9.0.0",
"type": "email"
}
},
{
"desc": "MailMate",
"ua": "MailMate/5663 (Macintosh; Intel Mac OS X 10.15.7)",
"expect": {
"name": "MailMate",
"version": "5663",
"type": "email"
}
},
{
"desc": "Mailspring",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Mailspring/1.7.8 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36",
"expect": {
"name": "Mailspring",
"version": "1.7.8",
"type": "email"
}
},
{
"desc": "Microsoft Outlook",
"ua": "Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.5431; Pro)",
"expect": {
"name": "Microsoft Outlook",
"version": "16.0.5431",
"type": "email"
}
},
{
"desc": "Microsoft Outlook for Mac",
"ua": "MacOutlook/14.7.1.161129 (Intel Mac OS X 10.9.6)",
"expect": {
"name": "MacOutlook",
"version": "14.7.1.161129",
"type": "email"
}
},
{
"desc": "Mutt",
"ua": "Mutt/1.5.21 (2010-09-15)",
"expect": {
"name": "Mutt",
"version": "1.5.21",
"type": "email"
}
},
{
"desc": "NaverMailApp",
"ua": "NaverMailApp/2.1.23 (Android 10; SM-N960N)",
"expect": {
"name": "NaverMailApp",
"version": "2.1.23",
"type": "email"
}
},
{
"desc": "Newton Mail",
"ua": "Newton/10.0.52 (Android 10; K)",
"expect": {
"name": "Newton",
"version": "10.0.52",
"type": "email"
}
},
{
"desc": "Nine",
"ua": "Nine/4.5.3a",
"expect": {
"name": "Nine",
"version": "4.5.3a",
"type": "email"
}
},
{
"desc": "Nylas Mail",
"ua": "NylasMail/2.0.32 (Macintosh; OS X 10.12.6)",
"expect": {
"name": "NylasMail",
"version": "2.0.32",
"type": "email"
}
},
{
"desc": "Outlook Express",
"ua": "Outlook-Express/6.0 (MSIE 6.0; Windows NT 5.1; SV1)",
"expect": {
"name": "Outlook-Express",
"version": "6.0",
"type": "email"
}
},
{
"desc": "Pegasus Mail",
"ua": "Pegasus Mail/4.70",
"expect": {
"name": "Pegasus Mail",
"version": "4.70",
"type": "email"
}
},
{
"desc": "PocoMail",
"ua": "PocoMail 4.8 (4320) - Licensed Version",
"expect": {
"name": "PocoMail",
"version": "4.8",
"type": "email"
}
},
{
"desc": "Polymail",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Polymail/2.3.12 Chrome/114.0.5735.134 Electron/25.2.0 Safari/537.36",
"expect": {
"name": "Polymail",
"version": "2.3.12",
"type": "email"
}
},
{
"desc": "Postbox",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Postbox/7.0.26",
"expect": {
"name": "Postbox",
"version": "7.0.26",
"type": "email"
}
},
{
"desc": "ProtonMail",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) ProtonMail/1.4.0 Chrome/122.0.6261.156 Electron/29.4.6 Safari/537.36",
"expect": {
"name": "ProtonMail",
"version": "1.4.0",
"type": "email"
}
},
{
"desc": "ProtonMail Bridge",
"ua": "ProtonMail Bridge/3.8.2 (Macintosh; Intel Mac OS X 10_15_7)",
"expect": {
"name": "ProtonMail Bridge",
"version": "3.8.2",
"type": "email"
}
},
{
"desc": "R2Mail2",
"ua": "R2Mail2/2.50 (Android)",
"expect": {
"name": "R2Mail2",
"version": "2.50",
"type": "email"
}
},
{
"desc": "RainLoop",
"ua": "RainLoop/1.17.0",
"expect": {
"name": "RainLoop",
"version": "1.17.0",
"type": "email"
}
},
{
"desc": "Roundcube Webmail",
"ua": "Roundcube Webmail/1.6.0",
"expect": {
"name": "Roundcube Webmail",
"version": "1.6.0",
"type": "email"
}
},
{
"desc": "Samsung Email",
"ua": "SamsungEmail/6.1.80.12",
"expect": {
"name": "SamsungEmail",
"version": "6.1.80.12",
"type": "email"
}
},
{
"desc": "SparkDesktop",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) SparkDesktop/3.0.30 Chrome/102.0.5005.63 Electron/19.0.4 Safari/537.36",
"expect": {
"name": "SparkDesktop",
"version": "3.0.30",
"type": "email"
}
},
{
"desc": "Sparrow",
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Sparrow/1043.1",
"expect": {
"name": "Sparrow",
"version": "1043.1",
"type": "email"
}
},
{
"desc": "Spicebird",
"ua": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.9pre) Gecko/20100216 Spicebird/0.8",
"expect": {
"name": "Spicebird",
"version": "0.8",
"type": "email"
}
},
{
"desc": "SquirrelMail",
"ua": "SquirrelMail/1.4.23 [SVN]",
"expect": {
"name": "SquirrelMail",
"version": "1.4.23",
"type": "email"
}
},
{
"desc": "Sylpheed",
"ua": "Sylpheed/3.7.0 (MinGW32; Windows NT 6.1; x86_64)",
"expect": {
"name": "Sylpheed",
"version": "3.7.0",
"type": "email"
}
},
{
"desc": "The Bat!",
"ua": "The Bat! 9.2.1",
"expect": {
"name": "The Bat!",
"version": "9.2.1",
"type": "email"
}
},
{
"desc": "Thunderbird",
"ua": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0",
"expect": {
"name": "Thunderbird",
"version": "78.13.0",
"type": "email"
}
},
{
"desc": "Trojita",
"ua": "Trojita/0.7",
"expect": {
"name": "Trojita",
"version": "0.7",
"type": "email"
}
},
{
"desc": "Turnpike",
"ua": "Turnpike/6.07-U",
"expect": {
"name": "Turnpike",
"version": "6.07-U",
"type": "email"
}
},
{
"desc": "Tuta Desktop (Tutanota)",
"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) tutanota-desktop/3.110.1 Chrome/110.0.5481.179 Electron/23.1.3 Safari/537.36",
"expect": {
"name": "tutanota-desktop",
"version": "3.110.1",
"type": "email"
}
},
{
"desc": "Wanderlust",
"ua": "Wanderlust/2.15.9 (Almost Unreal) SEMI/1.14.6 (Maruoka) FLIM/1.14.9 (Gojō) APEL/10.8 Emacs/23.1 (i486-pc-linux-gnu) MULE/6.0 (HANACHIRUSATO)",
"expect": {
"name": "Wanderlust",
"version": "2.15.9",
"type": "email"
}
},
{
"desc": "Windows Live Mail",
"ua": "Windows-Live-Mail/15.4.3508.1109",
"expect": {
"name": "Windows-Live-Mail",
"version": "15.4.3508.1109",
"type": "email"
}
},
{
"desc": "Yahoo! Mail (Android)",
"ua": "YahooMobile/1.0 (mail; 3.0.5.1311380); (Linux; U; Android 4.0.3; htc_runnymede Build/ICE_CREAM_SANDWICH_MR1);",
"expect": {
"name": "Yahoo Mail",
"version": "1.0",
"type": "email"
}
},
{
"desc": "Yahoo! Mail (Legacy)",
"ua": "YahooMobileMail/1.0 (Android Mail; 1.3.10) (supersonic;HTC;PC36100;2.3.5/GRJ90) ",
"expect": {
"name": "Yahoo Mail",
"version": "1.3.10",
"type": "email"
}
},
{
"desc": "Yahoo! Mail (iOS)",
"ua": "YahooMail/1.0 (iOS; 1.0.0; en_US)",
"expect": {
"name": "Yahoo Mail",
"version": "1.0",
"type": "email"
}
},
{
"desc": "Zimbra",
"ua": "Mozilla/5.0 (Windows; U; Windows NT 6.2; it; rv:1.9.2.19pre) Gecko/20110902 Prism zdesktop/7.2.8",
"expect": {
"name": "Zimbra",
"version": "7.2.8",
"type": "email"
}
},
{
"desc": "Zimbra (Server)",
"ua": "Zimbra/9.0.0_GA_4138",
"expect": {
"name": "Zimbra",
"version": "9.0.0_GA_4138",
"type": "email"
}
},
{
"desc": "ZohoMail",
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ZohoMail-Desktop/1.6.3 Chrome/124.0.6367.207 Electron/30.0.6 Safari/537.36",
"expect": {
"name": "ZohoMail-Desktop",
"version": "1.6.3",
"type": "email"
}
}
]

View File

@@ -51,7 +51,7 @@
},
{
"desc" : "Blueno",
"ua" : "acebookexternalhit/1.1 (compatible; Blueno/1.0; +http://naver.me/scrap)",
"ua" : "facebookexternalhit/1.1 (compatible; Blueno/1.0; +http://naver.me/scrap)",
"expect" :
{
"name" : "Blueno",
@@ -119,6 +119,16 @@
"type" : "fetcher"
}
},
{
"desc" : "Discordbot",
"ua" : "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)",
"expect" :
{
"name" : "Discordbot",
"version" : "2.0",
"type" : "fetcher"
}
},
{
"desc" : "DuckAssistBot",
"ua" : "DuckAssistBot/1.2; (+http://duckduckgo.com/duckassistbot.html)",
@@ -239,6 +249,16 @@
"type" : "fetcher"
}
},
{
"desc" : "KeybaseBot",
"ua" : "Mozilla/5.0 (compatible; KeybaseBot; +https://keybase.io)",
"expect" :
{
"name" : "KeybaseBot",
"version" : "undefined",
"type" : "fetcher"
}
},
{
"desc" : "Meta-ExternalFetcher",
"ua" : "meta-externalfetcher/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)",
@@ -269,6 +289,16 @@
"type" : "fetcher"
}
},
{
"desc" : "NovaAct",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Agent-NovaAct/0.9",
"expect" :
{
"name" : "NovaAct",
"version" : "0.9",
"type" : "fetcher"
}
},
{
"desc" : "Perplexity-User",
"ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Perplexity-User/1.0; +https://perplexity.ai/perplexity-user)",
@@ -309,6 +339,36 @@
"type" : "fetcher"
}
},
{
"desc" : "Slack-ImgProxy",
"ua" : "Slack-ImgProxy 0.19 (+https://api.slack.com/robots)",
"expect" :
{
"name" : "Slack-ImgProxy",
"version" : "0.19",
"type" : "fetcher"
}
},
{
"desc" : "Slackbot",
"ua" : "Slackbot 1.0 (+https://api.slack.com/robots)",
"expect" :
{
"name" : "Slackbot",
"version" : "1.0",
"type" : "fetcher"
}
},
{
"desc" : "Slackbot-LinkExpanding",
"ua" : "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)",
"expect" :
{
"name" : "Slackbot-LinkExpanding",
"version" : "1.0",
"type" : "fetcher"
}
},
{
"desc" : "Snap URL Preview",
"ua" : "Snap URL Preview Service; bot; snapchat; https://developers.snap.com/robots ",
@@ -339,6 +399,16 @@
"type" : "fetcher"
}
},
{
"desc" : "Twitterbot",
"ua" : "Twitterbot/1.0",
"expect" :
{
"name" : "Twitterbot",
"version" : "1.0",
"type" : "fetcher"
}
},
{
"desc" : "UptimeRobot",
"ua" : "Mozilla/5.0 (compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)",

View File

@@ -39,6 +39,36 @@
"type" : "library"
}
},
{
"desc" : "Bun",
"ua" : "Bun/1.0.6",
"expect" :
{
"name" : "Bun",
"version" : "1.0.6",
"type" : "library"
}
},
{
"desc" : "Dart",
"ua" : "Dart/3.5 (dart:io)",
"expect" :
{
"name" : "Dart",
"version" : "3.5",
"type" : "library"
}
},
{
"desc" : "Deno",
"ua" : "Deno/2.1.7",
"expect" :
{
"name" : "Deno",
"version" : "2.1.7",
"type" : "library"
}
},
{
"desc" : "go-http-client",
"ua" : "go-http-client/1.1",
@@ -69,6 +99,26 @@
"type" : "library"
}
},
{
"desc" : "hackney",
"ua" : "hackney/1.20.1",
"expect" :
{
"name" : "hackney",
"version" : "1.20.1",
"type" : "library"
}
},
{
"desc" : "http.rb",
"ua" : "http.rb/4.2.0",
"expect" :
{
"name" : "http.rb",
"version" : "4.2.0",
"type" : "library"
}
},
{
"desc" : "Java",
"ua" : "Java/1.6.0_14",
@@ -89,6 +139,16 @@
"type" : "library"
}
},
{
"desc" : "Jetty",
"ua" : "Jetty/11.0.13",
"expect" :
{
"name" : "Jetty",
"version" : "11.0.13",
"type" : "library"
}
},
{
"desc" : "jsdom",
"ua" : "Mozilla/5.0 (unknown OS) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/11.12.0",
@@ -130,22 +190,12 @@
}
},
{
"desc" : "Nutch",
"ua" : "AliyunSecBot/Nutch-1.21-SNAPSHOT",
"desc" : "Node.js",
"ua" : "Node.js/22",
"expect" :
{
"name" : "Nutch",
"version" : "1.21-SNAPSHOT",
"type" : "library"
}
},
{
"desc" : "OkHttp",
"ua" : "okhttp/3.2.0",
"expect" :
{
"name" : "okhttp",
"version" : "3.2.0",
"name" : "Node.js",
"version" : "22",
"type" : "library"
}
},
@@ -159,6 +209,36 @@
"type" : "library"
}
},
{
"desc" : "Nutch",
"ua" : "AliyunSecBot/Nutch-1.21-SNAPSHOT",
"expect" :
{
"name" : "Nutch",
"version" : "1.21-SNAPSHOT",
"type" : "library"
}
},
{
"desc" : "ocaml-cohttp",
"ua" : "ocaml-cohttp/1.2.00",
"expect" :
{
"name" : "ocaml-cohttp",
"version" : "1.2.00",
"type" : "library"
}
},
{
"desc" : "OkHttp",
"ua" : "okhttp/3.2.0",
"expect" :
{
"name" : "okhttp",
"version" : "3.2.0",
"type" : "library"
}
},
{
"desc" : "PHP-SOAP",
"ua" : "PHP-SOAP/7.4.33",
@@ -219,6 +299,16 @@
"type" : "library"
}
},
{
"desc" : "rest-client",
"ua" : "rest-client/2.1.0 (linux-gnu x86_64) ruby/2.7.2p137",
"expect" :
{
"name" : "rest-client",
"version" : "2.1.0",
"type" : "library"
}
},
{
"desc" : "Scrapy",
"ua" : "Scrapy/1.5.0 (+https://scrapy.org)",
@@ -238,5 +328,15 @@
"version" : "5.0.2",
"type" : "library"
}
},
{
"desc" : "undici",
"ua" : "undici",
"expect" :
{
"name" : "undici",
"version" : "undefined",
"type" : "library"
}
}
]

View File

@@ -25,5 +25,59 @@
"name" : "Firefox OS",
"version" : "44.0"
}
},
{
"desc" : "Firefox OS on Alcatel One Touch Fire",
"ua" : "Mozilla/5.0 (Mobile; ALCATELOneTouch4012X; rv:18.1) Gecko/18.1 Firefox/18.1",
"expect" :
{
"name" : "Firefox OS",
"version" : "18.1"
}
},
{
"desc" : "Firefox OS on Huawei Y300II",
"ua" : "Mozilla/5.0 (Mobile; HUAWEIY300-F1; rv:18.1) Gecko/18.1 Firefox/18.1",
"expect" :
{
"name" : "Firefox OS",
"version" : "18.1"
}
},
{
"desc" : "Firefox OS on LG Fireweb",
"ua" : "Mozilla/5.0 (Mobile; LG-D300; rv:18.1) Gecko/18.1 Firefox/18.1",
"expect" :
{
"name" : "Firefox OS",
"version" : "18.1"
}
},
{
"desc" : "Firefox OS on LG FX0",
"ua" : "Mozilla/5.0 (Mobile; LGL25; rv:32.0) Gecko/32.0 Firefox/32.0",
"expect" :
{
"name" : "Firefox OS",
"version" : "32.0"
}
},
{
"desc" : "Firefox OS on Panasonic Viera TV",
"ua" : "Mozilla/5.0 (Linux; Viera; rv:34.0) Gecko/20100101 Firefox/34.0",
"expect" :
{
"name" : "Firefox OS",
"version" : "34.0"
}
},
{
"desc" : "Firefox OS on ZTE Open",
"ua" : "Mozilla/5.0 (Mobile; ZTEOPEN; rv:18.1) Gecko/18.1 Firefox/18.1",
"expect" :
{
"name" : "Firefox OS",
"version" : "18.1"
}
}
]

View File

@@ -7,5 +7,14 @@
"name" : "FreeBSD",
"version" : "undefined"
}
},
{
"desc" : "FreeBSD",
"ua" : "Mozilla/5.0 (X11; FreeBSD; U; Viera; pl-PL) AppleWebKit/537.11 (KHTML, like Gecko) Viera/3.3.3 Chrome/23.0.1271.97 Safari/537.11",
"expect" :
{
"name" : "FreeBSD",
"version" : "undefined"
}
}
]

View File

@@ -1,4 +1,22 @@
[
{
"desc" : "iOS 18.6",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Mobile/15E148 Safari/604.1",
"expect" :
{
"name" : "iOS",
"version" : "18.6"
}
},
{
"desc" : "iOS 26",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1",
"expect" :
{
"name" : "iOS",
"version" : "26.0"
}
},
{
"desc" : "iOS in App",
"ua" : "AppName/version CFNetwork/version Darwin/version",
@@ -17,6 +35,15 @@
"version" : "5.1.1"
}
},
{
"desc" : "iOS with DuckDuckGo",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1 Ddg/26.0",
"expect" :
{
"name" : "iOS",
"version" : "18.7"
}
},
{
"desc" : "iOS with Opera Mini",
"ua" : "Opera/9.80 (iPhone; Opera Mini/7.1.32694/27.1407; U; en) Presto/2.8.119 Version/11.10",
@@ -35,6 +62,33 @@
"version" : "13.6.1"
}
},
{
"desc": "iOS with Instagram",
"ua": "Instagram 5.0.2 (iPhone5,1; iPhone OS 7_0_4; en_US; en) AppleWebKit/420+",
"expect":
{
"name" : "iOS",
"version" : "7.0.4"
}
},
{
"desc": "iOS with MS Word App",
"ua": "Microsoft Office Word/2.44.1211 (iOS/13.7; Tablet; es-MX; AppStore; Apple/iPad11,3)",
"expect":
{
"name" : "iOS",
"version" : "13.7"
}
},
{
"desc": "iOS with Quora App",
"ua": "Quora 8.4.30 rv:3230 env:prod (iPad11,3; iPadOS 17.7; en_GB) AppleWebKit",
"expect":
{
"name" : "iOS",
"version" : "17.7"
}
},
{
"desc": "iOS with Slack App",
"ua": "com.tinyspeck.chatlyio/23.04.10 (iPhone; iOS 16.4.1; Scale/3.00)",
@@ -44,6 +98,33 @@
"version" : "16.4.1"
}
},
{
"desc": "iOS with Snapchat",
"ua": "Snapchat/12.12.1.40 (iPhone15,2; iOS 16.2; gzip)",
"expect":
{
"name" : "iOS",
"version" : "16.2"
}
},
{
"desc": "iOS with Spotify App",
"ua": "Spotify/8.7.70 iOS/16.0 (iPhone15,3)",
"expect":
{
"name" : "iOS",
"version" : "16.0"
}
},
{
"desc": "iOS with TuneIn Radio App",
"ua": "TuneIn Radio/27.1.0; iPad6,3; iPadOS/16.6",
"expect":
{
"name" : "iOS",
"version" : "16.6"
}
},
{
"desc" : "iOS BE App",
"ua" : "APP-BE Test/1.0 (iPad; Apple; CPU iPhone OS 7_0_2 like Mac OS X)",
@@ -61,5 +142,41 @@
"name" : "iOS",
"version" : "11.2.5"
}
},
{
"desc": "iOS",
"ua": "iPlayTV/3.3.9 (Apple TV; iOS 16.1; Scale/1.00)",
"expect" :
{
"name" : "iOS",
"version" : "16.1"
}
},
{
"desc": "iOS",
"ua": "itunesstored/1.0 iOS/8.4.4 AppleTV/7.8 model/AppleTV3,2 build/12H937 (3; dt:12)",
"expect" :
{
"name" : "iOS",
"version" : "8.4.4"
}
},
{
"desc": "tvOS",
"ua": "iMPlayer/1.6.1 (tvOS 26.0.1)",
"expect" :
{
"name" : "iOS",
"version" : "26.0.1"
}
},
{
"desc": "tvOS",
"ua": "otg/1.5.1 (AppleTv Apple TV 4; tvOS16.2; appletv.client) libcurl/7.58.0 OpenSSL/1.0.2o zlib/1.2.11 clib/1.8.56",
"expect" :
{
"name" : "iOS",
"version" : "16.2"
}
}
]

View File

@@ -152,3 +152,45 @@ test.describe('request.headers can be passed in form of a Headers object', () =>
expect(uap.ua).toBe('myBrowser/1.0');
});
});
test.describe('Chaining withFeatureCheck() & withClientHints() in client-side development', () => {
test('Chain', async ({ page, browserName }) => {
await page.addInitScript((browserName) => {
Object.defineProperty(navigator, 'userAgent', {
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15'
});
Object.defineProperty(navigator, 'standalone', {
value: true
});
Object.defineProperty(navigator, 'maxTouchPoints', {
value: 3
});
if (browserName == 'chromium') {
Object.defineProperty(navigator, 'userAgentData', {
value: {
brands: [],
platform: '',
mobile: false,
getHighEntropyValues: () => {
return Promise.resolve({
formFactors: 'VR'
});
}
}
});
}
}, browserName);
await page.goto(localHtml);
// @ts-ignore
const fc2ch = await page.evaluate(async () => await UAParser().withFeatureCheck().then(res => res.withClientHints()));
const ch2fc = await page.evaluate(async () => await UAParser().withClientHints().then(res => res.withFeatureCheck()));
if (browserName == 'chromium') {
expect(fc2ch).toHaveProperty('device.type', 'xr'); // overwrite by client hints
expect(ch2fc).toHaveProperty('device.type', 'tablet'); // overwrite by feature check
} else {
expect(fc2ch).toHaveProperty('device.type', 'tablet'); // no client hints found
expect(ch2fc).toHaveProperty('device.type', 'tablet');
}
});
});

37
test/unit/cli/cli.spec.js Normal file
View File

@@ -0,0 +1,37 @@
const assert = require('node:assert');
const { exec } = require('node:child_process');
const fs = require('node:fs');
const { UAParser } = require('../../../src/main/ua-parser');
const uap = new UAParser();
const input = [
'Opera/9.25 (Windows NT 6.0; U; ru)',
'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
];
const output = input.map(x => uap.setUA(x).getResult());
describe('npx ua-parser-js <string>', () => {
it ('print result to stdout', () => {
exec('npx ua-parser-js "TEST"', (err, stdout, stderr) => {
assert.deepEqual(JSON.parse(stdout), JSON.parse(JSON.stringify([uap.setUA("TEST").getResult()])));
});
})
});
describe('npx ua-parser-js --input-file=<filepath>', () => {
it ('load file and print result to stdout', () => {
exec('npx ua-parser-js --input-file="../test/unit/cli/input.txt"', (err, stdout, stderr) => {
assert.deepEqual(JSON.parse(stdout), JSON.parse(JSON.stringify(output)));
});
});
});
describe('npx ua-parser-js --input-file=<filepath> --output-file=<filepath>', () => {
it ('load file and save result to file', () => {
exec('npx ua-parser-js --input-file="../test/unit/cli/input.txt" --output-file="../test/unit/cli/output.json"', (err, stdout, stderr) => {
fs.readFile('test/unit/cli/output.json', (err, data) => {
assert.deepEqual(JSON.parse(data), JSON.parse(JSON.stringify(output)));
});
});
});
});

2
test/unit/cli/input.txt Normal file
View File

@@ -0,0 +1,2 @@
Opera/9.25 (Windows NT 6.0; U; ru)
Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)

32
test/unit/cli/output.json Normal file
View File

@@ -0,0 +1,32 @@
[
{
"ua": "Opera/9.25 (Windows NT 6.0; U; ru)",
"browser": {
"name": "Opera",
"version": "9.25",
"major": "9"
},
"cpu": {},
"device": {},
"engine": {},
"os": {
"name": "Windows",
"version": "Vista"
}
},
{
"ua": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)",
"browser": {
"name": "IE",
"version": "5.5",
"major": "5"
},
"cpu": {},
"device": {},
"engine": {},
"os": {
"name": "Windows",
"version": "NT"
}
}
]

View File

@@ -1,85 +0,0 @@
const assert = require('assert');
const { UAParser } = require('../../src/main/ua-parser');
const { getDeviceVendor, isAppleSilicon, isAIBot, isBot, isChromeFamily } = require('../../src/helpers/ua-parser-helpers');
const { Bots, Emails } = require('../../src/extensions/ua-parser-extensions');
const { DeviceVendor } = require('../../src/enums/ua-parser-enums');
describe('getDeviceVendor', () => {
it('Can guess the device vendor from a model name', () => {
const modelSM = 'SM-A605G';
const modelRedmi = 'Redmi Note 8';
const modelNexus = 'Nexus 6P';
const modelAquos = 'AQUOS-TVX19B';
assert.equal(getDeviceVendor(modelSM), DeviceVendor.SAMSUNG);
assert.equal(getDeviceVendor(modelRedmi), DeviceVendor.XIAOMI);
assert.equal(getDeviceVendor(modelNexus), DeviceVendor.HUAWEI);
assert.equal(getDeviceVendor(modelAquos), DeviceVendor.SHARP);
});
});
describe('isAppleSilicon', () => {
it('Can detect Apple Silicon device', () => {
// non-real ua
const macARM = 'Mozilla/5.0 (Macintosh; ARM; Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
const macIntel = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
assert.equal(isAppleSilicon(UAParser(macIntel)), false);
assert.equal(isAppleSilicon(macIntel), false);
assert.equal(isAppleSilicon(UAParser(macARM)), true);
assert.equal(isAppleSilicon(macARM), true);
});
});
describe('isAIBot', () => {
it('Can detect AI Bots', () => {
const claudeBot = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
const searchGPT = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot';
const semrushAI = 'Mozilla/5.0 (compatible; SemrushBot-OCOB/1; +https://www.semrush.com/bot/)';
assert.equal(isAIBot(UAParser(claudeBot, Bots)), true);
assert.equal(isAIBot(claudeBot), true);
assert.equal(isAIBot(firefox), false);
assert.equal(isAIBot(searchGPT), true);
assert.equal(isAIBot(semrushAI), true);
});
});
describe('isBot', () => {
it('Can detect Bots', () => {
// non-real ua
const ahrefsBot = 'Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
const scrapy = 'Scrapy/1.5.0 (+https://scrapy.org)';
const thunderbird = 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0';
const botParser = new UAParser(firefox, { Bots, Emails });
assert.equal(isBot(botParser.getResult()), false);
assert.equal(isBot(botParser.setUA(ahrefsBot).getResult()), true);
assert.equal(isBot(botParser.setUA(scrapy).getResult()), true);
assert.equal(isBot(botParser.setUA(thunderbird).getResult()), false);
assert.equal(isBot(ahrefsBot), true);
assert.equal(isBot(firefox), false);
assert.equal(isBot(scrapy), true);
assert.equal(isBot(thunderbird), false);
});
});
describe('isChromeFamily', () => {
it('Can detect Chromium-based browser', () => {
const edge = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.2151.58';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
assert.equal(isChromeFamily(UAParser(edge)), true);
assert.equal(isChromeFamily(UAParser(firefox)), false);
assert.equal(isChromeFamily(edge), true);
assert.equal(isChromeFamily(firefox), false);
});
});

View File

@@ -10,7 +10,6 @@ var cpus = require('../data/ua/cpu/cpu-all.json');
var devices = readJsonFiles('test/data/ua/device');
var engines = require('../data/ua/engine/engine-all.json');
var os = readJsonFiles('test/data/ua/os');
var { Headers } = require('undici');
function readJsonFiles(dir) {
var list = [];
@@ -163,6 +162,14 @@ describe('Extending Regex', function () {
assert.deepEqual(myParser3.setUA(myUA2).getDevice(), {vendor: "MyTab", model: "14 Pro Max", type: "tablet"});
});
describe('User-agent with trailing space', function () {
it ('trailing space will be trimmed', function () {
const uastring = ' Opera/9.21 (Windows NT 5.1; U; ru) ';
const { ua } = UAParser(uastring);
assert.equal(ua, 'Opera/9.21 (Windows NT 5.1; U; ru) ');
});
});
describe('User-agent length', function () {
var UA_MAX_LENGTH = 500;
@@ -375,12 +382,15 @@ describe('Read user-agent data from req.headers', function () {
assert.strictEqual(engine.name, "EdgeHTML");
});
it('Fetch API\'s Header can be passed directly into headers', () => {
const reqHeaders = new Headers();
reqHeaders.append('User-Agent', 'Midori/0.2.2 (X11; Linux i686; U; en-us) WebKit/531.2+');
const { browser } = UAParser(reqHeaders);
assert.strictEqual(browser.is('Midori'), true);
});
// Headers supported in node 18+ - https://developer.mozilla.org/en-US/docs/Web/API/Headers
if (typeof Headers !== 'undefined') {
it('Fetch API\'s Header can be passed directly into headers', () => {
const reqHeaders = new Headers();
reqHeaders.append('User-Agent', 'Midori/0.2.2 (X11; Linux i686; U; en-us) WebKit/531.2+');
const { browser } = UAParser(reqHeaders);
assert.strictEqual(browser.is('Midori'), true);
});
}
it('Headers field name should be case insensitive', function () {
const hEaDeRs = {

View File

@@ -0,0 +1,42 @@
const assert = require('assert');
const { UAParser } = require('../../../src/main/ua-parser');
const { isAICrawler, isBot } = require('../../../src/bot-detection/bot-detection');
const { Bots, Emails } = require('../../../src/extensions/ua-parser-extensions');
describe('isAICrawler()', () => {
it('Can detect AI Crawlers', () => {
// AI Crawlers
const claudeBot = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)';
const searchGPT = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot';
const semrushAI = 'Mozilla/5.0 (compatible; SemrushBot-OCOB/1; +https://www.semrush.com/bot/)';
assert.equal(isAICrawler(claudeBot), true);
assert.equal(isAICrawler(searchGPT), true);
assert.equal(isAICrawler(semrushAI), true);
// Non-AI Crawlers
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
assert.equal(isAICrawler(firefox), false);
});
});
describe('isBot()', () => {
it('Can detect general Bots', () => {
// Bots
const ahrefsBot = 'Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)';
const scrapy = 'Scrapy/1.5.0 (+https://scrapy.org)';
assert.equal(isBot(ahrefsBot), true);
assert.equal(isBot(scrapy), true);
// Non-bots
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
const thunderbird = 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0';
assert.equal(isBot(firefox), false);
assert.equal(isBot(thunderbird), false);
});
});

View File

@@ -0,0 +1,16 @@
const assert = require('assert');
const { UAParser } = require('../../../src/main/ua-parser');
const { isChromeFamily } = require('../../../src/browser-detection/browser-detection');
describe('isChromeFamily()', () => {
it('Can detect Chromium-based browser', () => {
const edge = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.2151.58';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
assert.equal(isChromeFamily(UAParser(edge)), true);
assert.equal(isChromeFamily(edge), true);
assert.equal(isChromeFamily(UAParser(firefox)), false);
assert.equal(isChromeFamily(firefox), false);
});
});

View File

@@ -0,0 +1,32 @@
const assert = require('assert');
const { UAParser } = require('../../../src/main/ua-parser');
const { getDeviceVendor, isAppleSilicon } = require('../../../src/device-detection/device-detection');
const { DeviceVendor } = require('../../../src/enums/ua-parser-enums');
describe('getDeviceVendor()', () => {
it('Can guess the device vendor from a model name', () => {
const sm = 'SM-A605G';
const redmi = 'Redmi Note 8';
const nexus = 'Nexus 6P';
const aquos = 'AQUOS-TVX19B';
assert.equal(getDeviceVendor(sm), DeviceVendor.SAMSUNG);
assert.equal(getDeviceVendor(redmi), DeviceVendor.XIAOMI);
assert.equal(getDeviceVendor(nexus), DeviceVendor.HUAWEI);
assert.equal(getDeviceVendor(aquos), DeviceVendor.SHARP);
});
});
describe('isAppleSilicon()', () => {
it('Can detect Apple Silicon device', () => {
const macARM = 'Mozilla/5.0 (Macintosh; ARM; Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
const macIntel = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
assert.equal(isAppleSilicon(UAParser(macIntel)), false);
assert.equal(isAppleSilicon(macIntel), false);
assert.equal(isAppleSilicon(UAParser(macARM)), true);
assert.equal(isAppleSilicon(macARM), true);
});
});

View File

@@ -3,9 +3,9 @@ const assert = require('assert');
const parseJS = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const safe = require('safe-regex');
const { UAParser } = require('../../src/main/ua-parser');
const { Bots, CLIs, Crawlers, Emails, Fetchers, InApps, Libraries, Vehicles } = require('../../src/extensions/ua-parser-extensions');
const { BrowserType, OSName, Extension } = require('../../src/enums/ua-parser-enums');
const { UAParser } = require('../../../src/main/ua-parser');
const { Bots, CLIs, Crawlers, Emails, Fetchers, InApps, Libraries, Vehicles } = require('../../../src/extensions/ua-parser-extensions');
const { BrowserType, OSName, Extension } = require('../../../src/enums/ua-parser-enums');
const { CLI, Crawler, Email, Fetcher, Library } = Extension.BrowserName;
describe('Extensions', () => {
@@ -19,7 +19,7 @@ describe('Extensions', () => {
['Vehicles', 'vehicle', Vehicles]
]
.forEach(([desc, path, ext]) => {
const tests = require(`../data/ua/extension/${path}.json`);
const tests = require(`../../data/ua/extension/${path}.json`);
describe(desc, () => {
tests.forEach((test) => {
it(`Can detect ${test.desc}: "${test.ua}"`, () => {
@@ -38,18 +38,34 @@ describe('Extensions', () => {
});
});
// Existing test cases
const outlook = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; Microsoft Outlook 16.0.9126; Microsoft Outlook 16.0.9126; ms-office; MSOffice 16)';
const thunderbird = 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0';
const axios = 'axios/1.3.5';
const jsdom = 'Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/20.0.3';
const scrapy = 'Scrapy/1.5.0 (+https://scrapy.org)';
// New test cases for updated Regex logic
const macOutlook = 'MacOutlook/16.61.22041701 (Intel Mac OS X 10.15.7)';
const yahooMobile = 'YahooMobile/1.0 (mail; 3.0.5.1311380)';
assert.equal(UAParser(scrapy, Bots).browser.name, Library.SCRAPY);
const emailParser = new UAParser(Emails);
// Verify Standard Outlook
assert.deepEqual(emailParser.setUA(outlook).getBrowser(), {name: Email.MICROSOFT_OUTLOOK, version: "16.0.9126", major: "16", type: BrowserType.EMAIL});
// Verify Thunderbird
assert.deepEqual(emailParser.setUA(thunderbird).getBrowser(), {name: Email.THUNDERBIRD, version: "78.13.0", major: "78", type: BrowserType.EMAIL});
// Verify New MacOutlook Logic (Distinguishing it from Windows Outlook)
assert.deepEqual(emailParser.setUA(macOutlook).getBrowser(), {name: Email.MICROSOFT_OUTLOOK_MAC, version: "16.61.22041701", major: "16", type: BrowserType.EMAIL});
// Verify Yahoo Mobile Logic (Tightened Regex)
// Note: We expect 'Yahoo Mail' (Email.YAHOO_MAIL) because of the normalization helper.
assert.deepEqual(emailParser.setUA(yahooMobile).getBrowser(), {name: Email.YAHOO_MAIL, version: "1.0", major: "1", type: BrowserType.EMAIL});
const libraryParser = new UAParser(Libraries);
assert.deepEqual(libraryParser.setUA(axios).getBrowser(), {name: Library.AXIOS, version: "1.3.5", major: "1", type: BrowserType.LIBRARY});
assert.deepEqual(libraryParser.setUA(jsdom).getBrowser(), {name: Library.JSDOM, version: "20.0.3", major: "20", type: BrowserType.LIBRARY});

View File

@@ -0,0 +1,39 @@
const assert = require('assert');
const { isFrozenUA, getOutlookEdition } = require('../../../src/helpers/ua-parser-helpers');
describe('isFrozenUA()', () => {
it('matches supplied user-agent string with known frozen user-agent pattern', () => {
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 frozenMobileUA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36";
assert.equal(isFrozenUA(regularMobileUA), false);
assert.equal(isFrozenUA(frozenMobileUA), true);
});
});
describe('getOutlookEdition()', () => {
it('identifies Windows versions correctly', () => {
// MSI Version (Older engine)
assert.equal(getOutlookEdition('Microsoft Outlook', '16.0.4266.1001'), 'Outlook 2016 (MSI / Volume License)');
// Click-to-Run (Modern engine)
assert.equal(getOutlookEdition('Microsoft Outlook', '16.0.14326.20000'), 'Outlook 365 / 2019+ (Modern)');
// Legacy Major Version
assert.equal(getOutlookEdition('Microsoft Outlook', '15.0.4569.1506'), 'Outlook 2013');
});
it('identifies Mac versions correctly', () => {
assert.equal(getOutlookEdition('MacOutlook', '16.61'), 'Outlook for Mac (Modern)');
assert.equal(getOutlookEdition('MacOutlook', '15.4'), 'Outlook for Mac (Legacy)');
});
it('returns original name for unknown inputs', () => {
assert.equal(getOutlookEdition('Thunderbird', '91.0'), 'Thunderbird');
});
it('handles New Outlook (OneOutlook) correctly', () => {
// New Outlook usually sends a browser UA, but if it sends "Outlook" without version info matches,
// it shouldn't trigger the Legacy/MSI logic.
assert.equal(getOutlookEdition('Microsoft Outlook', 'SomeRandomString'), 'Microsoft Outlook');
});
});

View File

@@ -414,3 +414,19 @@ describe('Identify vendor & type of device from given model name', () => {
});
});
});
describe('Chaining withClientHints() & withFeatureCheck() in server-side development', () => {
const headers = {
'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15',
'sec-ch-ua-form-factors' : '"VR"'
};
const device = new UAParser(headers).getDevice();
it('Chain order: withFeatureCheck().withClientHints()', () => {
const fc2ch = device.withFeatureCheck().withClientHints();
assert.strictEqual(fc2ch.type, "xr");
});
it('Chain order: withClientHints().withFeatureCheck()', () => {
const ch2fc = device.withClientHints().withFeatureCheck();
assert.strictEqual(ch2fc.type, "xr");
});
});