diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 3ec50a0bb..b8b83265b 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,6 +1,6 @@
# These are supported funding model platforms
-github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+github: faisalman
patreon: # Replace with a single Patreon username
open_collective: ua-parser-js
ko_fi: # Replace with a single Ko-fi username
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..1705fdbe7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,43 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Library version**
+Which version of the library that you use, eg: v0.7.35 or v2.0.0-alpha.3
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen, or what's referred in the docs https://docs.uaparser.dev/
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+For issues related to detection results, you can send the screenshots of the demo section at https://uaparser.dev/#demo to confirm.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..bbcbbe7d6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000..c658b1889
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,22 @@
+# Prerequisites
+
+- [ ] I have read and follow the [contributing](https://github.com/faisalman/ua-parser-js/blob/master/CONTRIBUTING.md) guidelines
+- [ ] I have read and accept the [Contributor License Agreement (CLA)](https://gist.github.com/faisalman/2ed16621ebb544157eba85a7f7381417) Document and I hereby sign the CLA
+
+# Type of Change
+
+Bug fix, feature, docs update, ...
+
+# Description
+
+Please include a summary of the change (current behavior vs new behavior), which issue is fixed (you can also link to an open issue here), and why this change is necessary.
+
+# Test
+
+Please describe the tests that you ran to verify your changes.
+
+# Impact
+
+Does this PR introduce a breaking change? What changes might users need to make due to this PR?
+
+# Other Info
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 000000000..2df153f92
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1 @@
+- [ ] I have read and accept the [Contributor License Agreement (CLA)](https://gist.github.com/faisalman/2ed16621ebb544157eba85a7f7381417) Document and I hereby sign the CLA
\ No newline at end of file
diff --git a/.github/workflows/analysis-codeql.yml b/.github/workflows/analysis-codeql.yml
new file mode 100644
index 000000000..b718e6878
--- /dev/null
+++ b/.github/workflows/analysis-codeql.yml
@@ -0,0 +1,78 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: CodeQL Analysis
+
+on:
+ push:
+ branches: [ "master"]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "master" ]
+ schedule:
+ - cron: '15 6 * * 0'
+
+permissions: read-all
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'javascript' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Use only 'java' to analyze code written in Java, Kotlin or both
+ # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ # - name: Autobuild
+ # uses: github/codeql-action/autobuild@v2
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/analysis-dependency.yml b/.github/workflows/analysis-dependency.yml
new file mode 100644
index 000000000..99afdf01c
--- /dev/null
+++ b/.github/workflows/analysis-dependency.yml
@@ -0,0 +1,20 @@
+# Dependency Review Action
+#
+# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
+#
+# Source repository: https://github.com/actions/dependency-review-action
+# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
+name: Dependency Analysis
+on: [pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ dependency-review:
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Checkout Repository'
+ uses: actions/checkout@v3
+ - name: 'Dependency Review'
+ uses: actions/dependency-review-action@v2
diff --git a/.github/workflows/analysis-scorecard.yml b/.github/workflows/analysis-scorecard.yml
new file mode 100644
index 000000000..0c8f2b902
--- /dev/null
+++ b/.github/workflows/analysis-scorecard.yml
@@ -0,0 +1,72 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: OpenSSF's Scorecard Analysis
+on:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: '45 2 * * 4'
+ push:
+ branches: [ "master" ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ # contents: read
+ # actions: read
+
+ steps:
+ - name: "Checkout code"
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
+ # - you want to enable the Branch-Protection check on a *public* repository, or
+ # - you are installing Scorecard on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
+ repo_token: ${{ secrets.SCORECARD_TOKEN }}
+
+ # Public repositories:
+ # - Publish results to OpenSSF REST API for easy access by consumers
+ # - Allows the repository to include the Scorecard badge.
+ # - See https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories:
+ # - `publish_results` will always be set to `false`, regardless
+ # of the value entered here.
+ publish_results: true
+
+ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+ # format to the repository Actions tab.
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard.
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
+ with:
+ sarif_file: results.sarif
diff --git a/.github/workflows/publish-github-packages.yml b/.github/workflows/publish-github-packages.yml
new file mode 100644
index 000000000..25f1ca8f2
--- /dev/null
+++ b/.github/workflows/publish-github-packages.yml
@@ -0,0 +1,22 @@
+name: Publish to GitHub Package
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '18.x'
+ registry-url: https://npm.pkg.github.com/
+ - run: npm ci
+ - run: npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/publish-npm-packages.yml b/.github/workflows/publish-npm-packages.yml
new file mode 100644
index 000000000..b4f35d974
--- /dev/null
+++ b/.github/workflows/publish-npm-packages.yml
@@ -0,0 +1,23 @@
+name: Publish to NPM
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '18.x'
+ registry-url: 'https://registry.npmjs.org'
+ - run: npm install -g npm
+ - run: npm ci
+ - run: npm publish --provenance --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml
deleted file mode 100644
index 203184294..000000000
--- a/.github/workflows/run-test.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: ua-parser-js-run-test
-
-on: [push, pull_request]
-
-permissions:
- contents: read
-
-jobs:
- run-test:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- arch: [amd64, ppc64le]
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- - name: Run the test
- run: |
- npm install
- npm run test-ci
diff --git a/.github/workflows/test-ci.yml b/.github/workflows/test-ci.yml
new file mode 100644
index 000000000..f1e262b8b
--- /dev/null
+++ b/.github/workflows/test-ci.yml
@@ -0,0 +1,28 @@
+name: UAParser.js CI-Test
+
+on: [push, pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ run-test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ arch: [amd64]
+ node-version: ['22.13', 'lts/*']
+ include:
+ - arch: ppc64le
+ node-version: 20
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Run the test
+ run: |
+ echo "Running on ubuntu-latest-${{ matrix.arch }} with node version set as ${{ matrix.node-version }}"
+ npm ci
+ npx playwright install --with-deps
+ npm test
diff --git a/.gitignore b/.gitignore
index ca8c34e70..4a99144dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
node_modules/
npm-debug.log
+playwright-report/
+test-results/
### vim ###
.*.s[a-w][a-z]
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 5798043ca..000000000
--- a/.jshintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "esversion": 3
-}
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index 43c97e719..000000000
--- a/.npmrc
+++ /dev/null
@@ -1 +0,0 @@
-package-lock=false
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0f87fc37f..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-arch:
- - amd64
- - ppc64le
-language: node_js
-node_js:
- - stable
- - lts/*
-
-notifications:
- email: false
-
-cache:
- directories:
- - node_modules
-
-sudo: false
-
-script: npm run test-ci
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..d1f9ac63d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,345 @@
+# UAParser.js Changelog
+
+## Migrating from v1 to v2
+
+### What's Breaking:
+
+- **Licensing Changes:**
+ - UAParser.js is now licensed under AGPLv3 for open-source use, with PRO Licenses available for commercial/proprietary use
+
+- **Browser Detection on Mobile Devices:**
+ - `"Chrome"` => `"Mobile Chrome"`
+ - `"Firefox"` => `"Mobile Firefox"`
+
+- **OS Detection:**
+ - `"Mac OS"` => `"macOS"`
+ - `"Chromium OS"` => `"Chrome OS"`
+
+### What's New:
+
+- **Support for ES Modules & TypeScript:**
+ - Import directly as an ES module with TypeScript support: `import { UAParser } from 'ua-parser-js'`
+
+- **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]"`
+
+- **Enhanced Detection with Client Hints:**
+ - `withClientHints()`: Improves detection accuracy by leveraging client hints
+
+- **Enhanced Detection with Feature Detection:**
+ - `withFeatureCheck()`: Refines detection results using feature detection
+
+- **Simple Comparison for Detection Results:**
+ - `is()`: Enables easy comparison checks against the detection result
+
+- **Detailed Result Output:**
+ - `toString()`: Returns the detection result in form of a full-name string
+
+- **New Device Type:**
+ - Added `xr` to identify AR/VR devices
+
+- **New Browser Property:**
+ - Added `browser.type` to identify additional browser types:
+ - `crawler`, `cli`, `email`, `fetcher`, `inapp`, `library`, `mediaplayer`
+
+- **New Submodules:**
+ - **`'ua-parser-js/enums'`**: Provides constants for these specific properties:
+ - `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`
+
+ - **`'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
+ - `isBot()`: Checks if the user-agent is a bot
+ - `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
+
+---
+
+## Version 2.0.4
+
+- Add new browser: Edge WebView, Edge WebView2
+- Add new device vendor: Lava, Retroid, Vizio
+- Add new OS: ArcaOS, Knoppix, Xubuntu, Windows CE, Windows RT
+- Improve device detection: Google Pixel & Pixelbook Series, HMD, Infinix, LG WebOS TV, Motorola, Nothing, OnePlus, Sony, Tecno
+- Improve OS detection: AIX, Arch, Fuchsia, Haiku, HarmonyOS, Mint, MorphOS, Solaris, Windows
+- Improve `withClientHints()` browser naming adjustments: `Microsoft Edge WebView2` => `Edge WebView2`
+- Identify device that uses Firefox Reality / Wolvic as `xr`
+- Identify device with large screen as `smarttv`
+- Identify Windows CE & Windows RT as distinct OS variants
+- `extensions` submodule:
+ - Remove lookbehind assertion to ensure regex compatibility
+ - Add new crawlers: ChatGLM, Daum, iAskBot, Onespot, Qwantbot, Startpage
+ - Add new emails: Apple's Mail, DaumMail, Polymail, ProtonMail, SparkDesktop, Zimbra, ZohoMail-Desktop
+ - Add new fetchers: Iframely, MistralAI-User, Perplexity-User
+ - Add new inApps: Discord, Evernote, Figma, Flipboard, Mattermost, Notion, Postman, Rambox, Rocket.Chat, Microsoft Teams, TikTok Lite, VS Code
+ - Add new libraries: AdobeAIR, aiohttp, nutch, httpx, urllib3
+- `enum` submodule:
+ - Fix mistakenly placed `BLU` categorized as browser name instead of device vendor
+
+## Version 2.0.3
+
+- Add new browser: Dooble, Ecosia, LG Browser, Otter, qutebrowser, Surf
+- Add new device: BLU, Facebook Portal TV
+- Improve device detection: Archos, LG, Meta Quest
+- Remove jazzer.js fuzz test
+- Improve `withClientHints()`:
+ - Browser naming adjustments:
+ - `HuaweiBrowser` => `Huawei Browser`
+ - `Miui Browser` => `MIUI Browser`
+ - `OperaMobile` => `Opera Mobi`
+ - `YaBrowser` => `Yandex`
+- `extensions` submodule:
+ - Add new Crawler: AdIdxBot, Linespider, LinkedInBot, OpenAI Image Downloader, SemrushBot, Yahoo! Slurp
+ - Add new Fetcher: Better Uptime Bot, Google-PageRenderer, GoogleImageProxy, MicrosoftPreview, Snap URL Preview, SkypeUriPreview, TelegramBot
+ - Add new Vehicles: BMW, Jeep
+ - Add OS detection of WhatsApp user-agent
+
+## Version 2.0.2
+
+- Fix TypeScript dependency issue
+
+## Version 2.0.1
+
+- Add new browser: Ladybird, Daum
+- Add new device: Apple HomePod
+- Add new device vendor: HMD
+- Add new OS: Ubuntu Touch, Windows IoT
+- Improve CPU detection: ARM, x86
+- Improve device detection: Lenovo, Nokia, Nvidia, Xiaomi
+ - Tablet: Google, Honor, Huawei, Infinix, Nokia, OnePlus, Xiaomi
+ - Wearable: Asus, Google, LG, Motorola, OnePlus, Oppo, Samsung, Sony
+ - Smart-TV: Xiaomi, unidentified vendors
+ - Improve detection for unknown VR devices
+ - Improve device model detection for Generic devices
+- Improve OS detection: Linux, Symbian
+- Improve TypeScript definitions for Headers
+- Improve `withClientHints()`:
+ - `engine.version` also get updated
+ - Infer `device.vendor` & `device.type` by guessing from `device.model`
+ - Browser naming adjustments:
+ - `Google Chrome` => `Chrome`
+ - `Microsoft Edge` => `Edge`
+ - `Android WebView` => `Chrome WebView`
+ - `HeadlessChrome` => `Chrome Headless`
+- `enums` submodule:
+ - Add TypeScript definitions
+- `extensions` submodule:
+ - Add new list:
+ - `Vehicles`: BYD, Rivian, Volvo
+ - Add new Fetcher: Bluesky
+ - Add new Library: Apache-HttpClient, go-http-client, got, GuzzleHttp, Java-http-client, libwww-perl, lua-resty-http, Needle, OkHttp, node-fetch, PHP-SOAP, PostmanRuntime, superagent
+
+## Version 2.0.0
+
+- `ua-parser-js/extensions` submodule:
+ - Add new CLI: ELinks, HTTPie
+ - Add new crawler: AI2Bot, aiHitBot, anthropic-ai, Diffbot, ImagesiftBot, magpie-crawler, Omgilibot, Screaming Frog SEO Spider, Seznambot, Teoma, Timpibot, VelenPublicWebCrawler, Webzio-Extended, YouBot
+ - Add new email: Airmail, BlueMail, eMClient, NaverMailApp, Sparrow, Yahoo
+ - Add new fetcher: cohere-ai, Vercelbot
+ - Add new library: java, python-urllib, python-requests
+- `ua-parser-js/helpers` submodule:
+ - Add new method `isAIBot()`: Checks if the user-agent is an AI bot
+
+## Version 2.0.0-rc.3
+
+- Add support for Headers object
+- Add new device: Advan, Cat, Energizer, Honor, IMO, Micromax, Smartfren
+- Add new engine: Servo
+- `ua-parser-js/extensions` submodule:
+ - Breaking change: rename `module` to `library`
+ - Add new email clients: Evolution, KMail, Kontact
+ - Add new bots: 360Spider, Archive.org Bots, CCBot, DataForSeoBot, DuckAssistBot, Exabot, Google Bots, Meta Bots, MojeekBot, PerplexityBot, PetalBot, TurnitinBot, Yeti, YisouSpider
+
+## Version 2.0.0-rc.2
+
+- Fix incorrect import path in ESM files
+- Add new browser: 115, SlimBoat, Slimjet, LibreWolf
+- Improve browser detection: 2345, 360, Dragon, Iron, Maxthon
+- `ua-parser-js/enums` submodule:
+ - Add Chromecast OS variants: Android/Fuchsia/Linux/SmartSpeaker
+- `ua-parser-js/helpers` submodule:
+ - Add new method: `isBot()` to check if the browser is identified as a bot
+
+## Version 2.0.0-rc.1
+
+- Fix Python Request mistakenly identified as Meta Quest
+- Add new browser: Helio
+- Add new device: itel, Nothing, Pico, TCL
+- Add new engine: ArkWeb
+- Add new OS: OpenHarmony, Pico
+- Improve browser detection: Quark
+- Improve device detection: Xiaomi, Amazon Echo Show, Google Chromecast, Samsung Galaxy Watch
+- `ua-parser-js/helpers` submodule:
+ - Add new method:
+ - `getDeviceVendor()` to guess for a device vendor based on its model name
+ - `isElectron()` to check if current window is running inside Electron
+ - `isFromEU()` to check if current window is from an EU (European Union) country
+ - `isStandalonePWA()` to check if current window is a standalone PWA
+ - Rename `isChromiumBased()` to `isChromeFamily()`
+ - Update `isAppleSilicon()` to also checks for WebGL renderer info
+- `ua-parser-js/extensions` submodule:
+ - Restore `Bots` as a compilation of all these browser types: `cli`, `crawler`, `fetcher`, and `library`
+
+## Version 2.0.0-beta.3
+
+- Breaking:
+ - AR/VR devices moved to new device type: `xr`
+ - New property in `browser`: `type`
+- New features:
+ - Parse directly from command line using `npx ua-parser-js`
+ - Extensions can be passed as a list to `UAParser()`
+- Add new browser: Pico Browser, Twitter, Wolvic
+- Improve browser detection: DuckDuckGo, ICEBrowser, Klar, QQ, Sleipnir
+- Improve device detection: Oculus Quest & Oppo Pad
+- Update latest client hints spec: `formFactor` -> `formFactors`
+- In `ua-parser-js/extensions` submodule, `bots` divided into `crawler` / `fetcher`
+
+## Version 2.0.0-beta.2
+
+- Increase UA_MAX_LENGTH to 500
+- Add TypeScript declaration file in `ua-parser-js/extensions` submodule
+- Improve TypeScript module resolution
+- Add new methods in `ua-parser-js/helpers` submodule: `isAppleSilicon()` & `isChromiumBased()`
+- Fix misidentified WebView token as device model
+- Add new browser: Alipay, Klarna, Opera GX, Smart Lenovo Browser, Vivo Browser
+- Rename browser: Avant, Baidu, Samsung Internet, Sogou Explorer, Sogou Mobile, WeChat
+- Improve client-hints detection: Edge, Xbox
+
+## Version 2.0.0-beta.1
+
+- Update Client Hints Form-Factor
+- Provide in-package type definitions
+- Add new device: Ulefone
+- Improve device detection: Realme, Xiaomi Redmi
+
+## Version 2.0.0-alpha.3
+
+- Add `withFeatureCheck()` method
+- Add `isFrozenUA()` method in `ua-parser-js/helpers` submodule
+- Add `MediaPlayers` & `Modules` in `ua-parser-js/extensions` submodule
+- Fix issue with ESM import
+
+## Version 2.0.0-alpha.2
+
+- Fix browser result always returning Chromium when using withClientHints()
+- Fix infinite-loop when await-ing withClientHints() in non-client-hints browser
+
+## Version 2.0.0-alpha.1
+
+- Initial work on new major version
+
+---
+
+## Version 0.7.40 / 1.0.40
+- Add new browser: 115, LibreWolf, Slimboat, Slimjet
+- Add new device: Advan, Cat, Energizer, IMO, Micromax, Smartfren
+- Add new engine: ArkWeb, Servo
+- Add new os: OpenHarmony
+- Improve browser detection: 2345, 360, Dragon, Iron, Maxthon
+- Recognize Honor as a separate device vendor from Huawei
+- Fix Python Request mistakenly identified as Meta Quest
+
+## Version 0.7.39 / 1.0.39
+- Add new feature: executable command using `npx ua-parser-js "[INSERT-UA-HERE]"`
+- Add new browser: Helio, Pico Browser, Wolvic
+- Add new device vendor: itel, Nothing, TCL
+- Improve browser detection: ICEBrowser, Klar, QQBrowser, Quark, Rekonq, Sleipnir
+- Improve device detection: Xiaomi Pro, Amazon Echo Show, Samsung Galaxy Watch
+- Removed from browser: Viera
+
+## Version 0.7.38 / 1.0.38
+- Fix error on getOS() when userAgentData.platform is undefined
+- Add new browser: Opera GX, Twitter
+- Improve browser detection: DuckDuckGo
+- Improve device detection: OPPO Pad, Oculus Quest
+
+## Version 0.7.37 / 1.0.37
+- Fix misidentified WebView token as device model
+- Increase UA_MAX_LENGTH to 500
+- Add new browser: Alipay, Klarna, Smart Lenovo Browser, Vivo Browser
+- Add new device: Ulefone
+- Improve device detection: Realme, Xiaomi Redmi
+- Rename browser: Avant, Baidu, Samsung Internet, Sogou Explorer, Sogou Mobile, WeChat
+
+## Version 0.7.36 / 1.0.36
+- Add new browser: Snapchat
+- Add new devices: Infinix, Tecno
+- Improve device detection: Amazon Fire TV, Xiaomi POCO
+- Improve OS detection: iOS
+
+## Version 0.7.35 / 1.0.35
+- Fix result from user-supplied user-agent being altered
+- Add new browser: Heytap, TikTok
+- Add new engine: LibWeb
+- Add new OS: SerenityOS
+- Improve browser detection: Yandex
+- Improve device detection: iPhone, Amazon Echo
+- Improve OS detection: iOS
+
+## Version 0.7.34 / 1.0.34
+- Fix Sharp Mobile detected as Huawei Tablet
+- Fix IE8 bug
+- Add new devices : Kobo e-Reader, Apple Watch, and some new SmartTV devices
+- Add new OS : watchOS
+- Improve browser detection : Kakao, Naver, Brave
+- Improve device detection : Oculus, iPad
+- Improve OS detection : Chrome OS
+- Using navigator.userAgentData as fallback for device.type & os.name
+
+## Version 0.7.33 / 1.0.33
+
+- Add new browser : Cobalt
+- Identify Macintosh as an Apple device
+- Fix ReDoS vulnerability
+
+## Version 0.7.32 / 1.0.32
+
+- Add new browser : DuckDuckGo, Huawei Browser, LinkedIn
+- Add new OS : HarmonyOS
+- Add some Huawei models
+- Add Sharp Aquos TV
+- Improve detection Xiaomi Mi CC9
+- Fix Sony Xperia 1 III misidentified as Acer tablet
+- Fix Detect Sony BRAVIA as SmartTV
+- Fix Detect Xiaomi Mi TV as SmartTV
+- Fix Detect Galaxy Tab S8 as tablet
+- Fix WeGame mistakenly identified as WeChat
+- Fix included commas in Safari / Mobile Safari version
+- Increase UA_MAX_LENGTH to 350
+
+## Version 0.7.31 / 1.0.2
+
+- Fix OPPO Reno A5 incorrect detection
+- Fix TypeError Bug
+- Use AST to extract regexes and verify them with safe-regex
+
+## Version 0.7.30 / 1.0.1
+
+- Add new browser : Obigo, UP.Browser, Klar
+- Add new device : Oculus, Roku
+- Add new OS: Maemo, HP-UX, Android-x86, Deepin, elementary OS, GhostBSD, Linspire, Manjaro, Sabayon
+- Improve detection for Sony Xperia 1ii, LG Android TV, and some more devices
+- Improve detection for ARM64 CPU
+- Improve detection for Windows Mobile, Netscape, Mac on PowerPC
+- Categorize PDA as mobile
+- Fix Sharp devices misjudged as Huawei
+- Fix trailing comma for ES3 compatibility
+- Some code refactor
+
+## Version 0.7 / 1.0
+
+Version 1.0.x is basically the equivalent of version 0.7.x (mirror/duplicate). See [#536](https://github.com/faisalman/ua-parser-js/issues/536) for the reason behind this confusion.
+
+## Version 0.8
+
+Version 0.8 was created by accident. This version is now deprecated and no longer maintained, please update to version 0.7 / 1.0.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..b148af6e5
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,29 @@
+# UAParser.js Code of Conduct
+
+## Introduction
+
+Welcome to the UAParser.js community! We're here to collaborate on developing an awesome project. Here are some general guidelines to make our community a great place:
+
+### 1. Be Kind, Honest, and Respectful
+
+Always treat others with kindness and respect. We value different opinions and encourage positive communication.
+
+### 2. Keep Conversations Civil and On-Topic
+
+Please keep discussions related to the project. If you want to talk about something else, find the right place for it.
+
+### 3. Mutual Assistance, Appreciation, and Acknowledgement
+
+Feel free to ask for help, show gratitude for contributions, and make sure to give credit where it's due.
+
+### 4. Resolving Disagreements
+
+In the event of a disagreement, we encourage open and respectful dialogue. It's important to remember that it's okay to have differing opinions, and if a common ground can't be reached, we suggest using the 'agree to disagree' approach.
+
+## Reporting Issues
+
+If you see any behavior that goes against this code of conduct, report it to [f@faisalman.com](mailto:f@faisalman.com).
+
+## Conclusion
+
+Together, we can make this project awesome!
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..0bb377bc7
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,7 @@
+# UAParser.js: How to Contribute
+
+* Fork and clone this repository
+* Make some changes as required
+* Write unit test to showcase its functionality under `/test`
+* Run the test suites to make sure it's not breaking anything `$ npm run build+test`
+* Submit a pull request & check the CLA in the submission form
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 000000000..213ad0b2e
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,616 @@
+# GNU AFFERO GENERAL PUBLIC LICENSE
+
+Version 3, 19 November 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc.
+
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+## Preamble
+
+The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains
+free software for all its users.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing
+under this license.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+## TERMS AND CONDITIONS
+
+### 0. Definitions.
+
+"This License" refers to version 3 of the GNU Affero General Public
+License.
+
+"Copyright" also means copyright-like laws that apply to other kinds
+of works, such as semiconductor masks.
+
+"The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of
+an exact copy. The resulting work is called a "modified version" of
+the earlier work or a work "based on" the earlier work.
+
+A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user
+through a computer network, with no transfer of a copy, is not
+conveying.
+
+An interactive user interface displays "Appropriate Legal Notices" to
+the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+### 1. Source Code.
+
+The "source code" for a work means the preferred form of the work for
+making modifications to it. "Object code" means any non-source form of
+a work.
+
+A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can
+regenerate automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same
+work.
+
+### 2. Basic Permissions.
+
+All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey,
+without conditions so long as your license otherwise remains in force.
+You may convey covered works to others for the sole purpose of having
+them make modifications exclusively for you, or provide you with
+facilities for running those works, provided that you comply with the
+terms of this License in conveying all material for which you do not
+control copyright. Those thus making or running the covered works for
+you must do so exclusively on your behalf, under your direction and
+control, on terms that prohibit them from making any copies of your
+copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the
+conditions stated below. Sublicensing is not allowed; section 10 makes
+it unnecessary.
+
+### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such
+circumvention is effected by exercising rights under this License with
+respect to the covered work, and you disclaim any intention to limit
+operation or modification of the work as a means of enforcing, against
+the work's users, your or third parties' legal rights to forbid
+circumvention of technological measures.
+
+### 4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+### 5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these
+conditions:
+
+- a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+- b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under
+ section 7. This requirement modifies the requirement in section 4
+ to "keep intact all notices".
+- c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+- d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+### 6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms of
+sections 4 and 5, provided that you also convey the machine-readable
+Corresponding Source under the terms of this License, in one of these
+ways:
+
+- a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+- b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the Corresponding
+ Source from a network server at no charge.
+- c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+- d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+- e) Convey the object code using peer-to-peer transmission,
+ provided you inform other peers where the object code and
+ Corresponding Source of the work are being offered to the general
+ public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal,
+family, or household purposes, or (2) anything designed or sold for
+incorporation into a dwelling. In determining whether a product is a
+consumer product, doubtful cases shall be resolved in favor of
+coverage. For a particular product received by a particular user,
+"normally used" refers to a typical or common use of that class of
+product, regardless of the status of the particular user or of the way
+in which the particular user actually uses, or expects or is expected
+to use, the product. A product is a consumer product regardless of
+whether the product has substantial commercial, industrial or
+non-consumer uses, unless such uses represent the only significant
+mode of use of the product.
+
+"Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to
+install and execute modified versions of a covered work in that User
+Product from a modified version of its Corresponding Source. The
+information must suffice to ensure that the continued functioning of
+the modified object code is in no case prevented or interfered with
+solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or
+updates for a work that has been modified or installed by the
+recipient, or for the User Product in which it has been modified or
+installed. Access to a network may be denied when the modification
+itself materially and adversely affects the operation of the network
+or violates the rules and protocols for communication across the
+network.
+
+Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+### 7. Additional Terms.
+
+"Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders
+of that material) supplement the terms of this License with terms:
+
+- a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+- b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+- c) Prohibiting misrepresentation of the origin of that material,
+ or requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+- d) Limiting the use for publicity purposes of names of licensors
+ or authors of the material; or
+- e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+- f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions
+ of it) with contractual assumptions of liability to the recipient,
+ for any liability that these contractual assumptions directly
+ impose on those licensors and authors.
+
+All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions; the
+above requirements apply either way.
+
+### 8. Termination.
+
+You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+However, if you cease all violation of this License, then your license
+from a particular copyright holder is reinstated (a) provisionally,
+unless and until the copyright holder explicitly and finally
+terminates your license, and (b) permanently, if the copyright holder
+fails to notify you of the violation by some reasonable means prior to
+60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+### 9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run
+a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+### 10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+### 11. Patents.
+
+A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+A contributor's "essential patent claims" are all patent claims owned
+or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+A patent license is "discriminatory" if it does not include within the
+scope of its coverage, prohibits the exercise of, or is conditioned on
+the non-exercise of one or more of the rights that are specifically
+granted under this License. You may not convey a covered work if you
+are a party to an arrangement with a third party that is in the
+business of distributing software, under which you make payment to the
+third party based on the extent of your activity of conveying the
+work, and under which the third party grants, to any of the parties
+who would receive the covered work from you, a discriminatory patent
+license (a) in connection with copies of the covered work conveyed by
+you (or copies made from those copies), or (b) primarily for and in
+connection with specific products or compilations that contain the
+covered work, unless you entered into that arrangement, or that patent
+license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+### 12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under
+this License and any other pertinent obligations, then as a
+consequence you may not convey it at all. For example, if you agree to
+terms that obligate you to collect a royalty for further conveying
+from those to whom you convey the Program, the only way you could
+satisfy both those terms and this License would be to refrain entirely
+from conveying the Program.
+
+### 13. Remote Network Interaction; Use with the GNU General Public License.
+
+Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your
+version supports such interaction) an opportunity to receive the
+Corresponding Source of your version by providing access to the
+Corresponding Source from a network server at no charge, through some
+standard or customary means of facilitating copying of software. This
+Corresponding Source shall include the Corresponding Source for any
+work covered by version 3 of the GNU General Public License that is
+incorporated pursuant to the following paragraph.
+
+Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+### 14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions
+of the GNU Affero General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever
+published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions
+of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+### 15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
+WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
+PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
+DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+### 16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
+CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
+NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
+LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
+TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
+PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+### 17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..0ea5465c7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,251 @@
+[](https://uaparser.dev)
+[](https://uaparser.dev)
+[](https://uaparser.dev)
+[](https://uaparser.dev)
+
+
+
+
+
+
+
+
+
+
+
+
+# UAParser.js
+
+The most comprehensive, compact, & up-to-date isomorphic JavaScript library to detect
+user's Browser, Engine, OS, CPU, and Device type/model. Runs either in browser
+(client-side) or node.js (server-side).
+
+# Demo
+
+ * Live demo: https://uaparser.dev
+
+
+# Documentation
+
+ * `version 1.x` : https://github.com/faisalman/ua-parser-js/tree/1.0.x#documentation
+ * `version 2.x` : https://docs.uaparser.dev
+
+Before upgrading from `v0.7` / `v1.0`, please read [CHANGELOG](CHANGELOG.md) to
+see what's new & breaking.
+
+# License Options
+
+
+
+
+
+ Open-Source Editions
+ PRO / Commercial Editions
+
+
+
+
+ License options
+ MIT (v0.7~v1.0)
+ AGPL (>=v2.0)
+ PRO Personal
+ PRO Business
+ PRO Enterprise
+
+
+ Browser detection
+ â ī¸
+ â
+ â
+ â
+ â
+
+
+ CPU detection
+ â ī¸
+ â
+ â
+ â
+ â
+
+
+ Device detection
+ â ī¸
+ â
+ â
+ â
+ â
+
+
+ Engine detection
+ â ī¸
+ â
+ â
+ â
+ â
+
+
+ OS detection
+ â ī¸
+ â
+ â
+ â
+ â
+
+
+ Bot detection
+ â
+ â
+ â
+ â
+ â
+
+
+ AI Bot detection
+ â
+ â
+ â
+ â
+ â
+
+
+ Extras (Apps, Libs, Emails, Media Players, etc) detection
+ â
+ â
+ â
+ â
+ â
+
+
+ Enhanced detection result
+ â
+ â
+ â
+ â
+ â
+
+
+ Client Hints support
+ â
+ â
+ â
+ â
+ â
+
+
+ CommonJS support
+ â
+ â
+ â
+ â
+ â
+
+
+ ES modules support
+ â
+ â
+ â
+ â
+ â
+
+
+ npm module
+ â
+ â
+ â
+ â
+ â
+
+
+ TypeScript declarations
+ â ī¸
+ â
+ â
+ â
+ â
+
+
+ Allows commercial use
+ â
+ â
+ â
+ â
+ â
+
+
+ Permissive (non-copyleft) license
+ â
+ â
+ â
+ â
+ â
+
+
+ Unlimited use per 1 license
+ â
+ â
+ â
+ â
+ â
+
+
+ 1-year product support
+ â
+ â
+ â
+ â
+ â
+
+
+ Lifetime updates
+ â
+ â
+ â
+ â
+ â
+
+
+ Price
+ FREE (License )
+ FREE (License )
+ $14 (License )
+ $29 (License )
+ $599 (License )
+
+
+
+
+
+
+
+
+
+
+
+# Development
+
+## Contributors
+
+Please read [CONTRIBUTING](CONTRIBUTING.md) guide first for the instruction details.
+
+
+
+
+
+Made with [contributors-img](https://contrib.rocks).
+
+## Backers & Sponsors
+
+
+
+
+You can support the open-source editions of UAParser.js through one of the following options:
+
+[](https://opencollective.com/ua-parser-js)
+[](https://github.com/sponsors/faisalman)
+[](https://paypal.me/faisalman)
+[](https://store.faisalman.com/buy/3d71f2f3-cf4d-473c-892a-9d4497c890be)
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..d1f40fe80
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,7 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+To report a security issue, please email `f@faisalman.com` with a description of the issue, reproducible steps to get the issue, affected versions, and, if known, mitigations for the issue.
+
+If the issue is confirmed as a vulnerability, we will open a new security advisory draft in our GitHub's Security Advisory page [https://github.com/faisalman/ua-parser-js/security/advisories](https://github.com/faisalman/ua-parser-js/security/advisories) and acknowledge your contributions as part of it. This project follows a 90 days disclosure timeline.
\ No newline at end of file
diff --git a/bower.json b/bower.json
deleted file mode 100644
index c59773a0e..000000000
--- a/bower.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "ua-parser-js",
- "version": "0.7.33",
- "authors": [
- "Faisal Salman "
- ],
- "private": false,
- "main": "src/ua-parser.js",
- "ignore": [
- "build",
- "node_modules",
- "bower_components",
- "test",
- "tests"
- ],
- "dependencies": {}
-}
diff --git a/changelog.md b/changelog.md
deleted file mode 100644
index 05fc6b90b..000000000
--- a/changelog.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# UAParser.js Changelog
-
-## Version 0.8.0
\ No newline at end of file
diff --git a/dist/icons/color/LICENSE.md b/dist/icons/color/LICENSE.md
new file mode 100644
index 000000000..c025cd011
--- /dev/null
+++ b/dist/icons/color/LICENSE.md
@@ -0,0 +1,3 @@
+MIT License
+Copyright (c) CÄtÄlin MariČ
+https://github.com/alrra/browser-logos
\ No newline at end of file
diff --git a/dist/icons/color/browser/android-browser.svg b/dist/icons/color/browser/android-browser.svg
new file mode 100644
index 000000000..92d59275b
--- /dev/null
+++ b/dist/icons/color/browser/android-browser.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/avant.png b/dist/icons/color/browser/avant.png
new file mode 100644
index 000000000..e0b715d8b
Binary files /dev/null and b/dist/icons/color/browser/avant.png differ
diff --git a/dist/icons/color/browser/basilisk.png b/dist/icons/color/browser/basilisk.png
new file mode 100644
index 000000000..19a59d65b
Binary files /dev/null and b/dist/icons/color/browser/basilisk.png differ
diff --git a/dist/icons/color/browser/basilisk.svg b/dist/icons/color/browser/basilisk.svg
new file mode 100644
index 000000000..64e1f61c3
--- /dev/null
+++ b/dist/icons/color/browser/basilisk.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/brave.png b/dist/icons/color/browser/brave.png
new file mode 100644
index 000000000..06fa47770
Binary files /dev/null and b/dist/icons/color/browser/brave.png differ
diff --git a/dist/icons/color/browser/brave.svg b/dist/icons/color/browser/brave.svg
new file mode 100644
index 000000000..fb5ed8b82
--- /dev/null
+++ b/dist/icons/color/browser/brave.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/chrome.png b/dist/icons/color/browser/chrome.png
new file mode 100644
index 000000000..f251ad8e9
Binary files /dev/null and b/dist/icons/color/browser/chrome.png differ
diff --git a/dist/icons/color/browser/chrome.svg b/dist/icons/color/browser/chrome.svg
new file mode 100644
index 000000000..dcc3b1106
--- /dev/null
+++ b/dist/icons/color/browser/chrome.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/chromium.png b/dist/icons/color/browser/chromium.png
new file mode 100644
index 000000000..7a3cb15fc
Binary files /dev/null and b/dist/icons/color/browser/chromium.png differ
diff --git a/dist/icons/color/browser/chromium.svg b/dist/icons/color/browser/chromium.svg
new file mode 100644
index 000000000..97b24f226
--- /dev/null
+++ b/dist/icons/color/browser/chromium.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/coc-coc.png b/dist/icons/color/browser/coc-coc.png
new file mode 100644
index 000000000..f917dbcab
Binary files /dev/null and b/dist/icons/color/browser/coc-coc.png differ
diff --git a/dist/icons/color/browser/default.png b/dist/icons/color/browser/default.png
new file mode 100644
index 000000000..61e01e6a3
Binary files /dev/null and b/dist/icons/color/browser/default.png differ
diff --git a/dist/icons/color/browser/default.svg b/dist/icons/color/browser/default.svg
new file mode 100644
index 000000000..4d9afe0fd
--- /dev/null
+++ b/dist/icons/color/browser/default.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/dolphin.png b/dist/icons/color/browser/dolphin.png
new file mode 100644
index 000000000..d53805f98
Binary files /dev/null and b/dist/icons/color/browser/dolphin.png differ
diff --git a/dist/icons/color/browser/edge.png b/dist/icons/color/browser/edge.png
new file mode 100644
index 000000000..931cb1c1b
Binary files /dev/null and b/dist/icons/color/browser/edge.png differ
diff --git a/dist/icons/color/browser/edge.svg b/dist/icons/color/browser/edge.svg
new file mode 100644
index 000000000..b2afc8e2d
--- /dev/null
+++ b/dist/icons/color/browser/edge.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/electron.png b/dist/icons/color/browser/electron.png
new file mode 100644
index 000000000..e17799028
Binary files /dev/null and b/dist/icons/color/browser/electron.png differ
diff --git a/dist/icons/color/browser/electron.svg b/dist/icons/color/browser/electron.svg
new file mode 100644
index 000000000..a766fedd9
--- /dev/null
+++ b/dist/icons/color/browser/electron.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/falkon.png b/dist/icons/color/browser/falkon.png
new file mode 100644
index 000000000..d7fe06de5
Binary files /dev/null and b/dist/icons/color/browser/falkon.png differ
diff --git a/dist/icons/color/browser/falkon.svg b/dist/icons/color/browser/falkon.svg
new file mode 100644
index 000000000..0a2e4890f
--- /dev/null
+++ b/dist/icons/color/browser/falkon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/firefox-reality.png b/dist/icons/color/browser/firefox-reality.png
new file mode 100644
index 000000000..9a901a445
Binary files /dev/null and b/dist/icons/color/browser/firefox-reality.png differ
diff --git a/dist/icons/color/browser/firefox-reality.svg b/dist/icons/color/browser/firefox-reality.svg
new file mode 100644
index 000000000..a157540ab
--- /dev/null
+++ b/dist/icons/color/browser/firefox-reality.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/firefox.png b/dist/icons/color/browser/firefox.png
new file mode 100644
index 000000000..fc7123205
Binary files /dev/null and b/dist/icons/color/browser/firefox.png differ
diff --git a/dist/icons/color/browser/firefox.svg b/dist/icons/color/browser/firefox.svg
new file mode 100644
index 000000000..f153cedd9
--- /dev/null
+++ b/dist/icons/color/browser/firefox.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/gsa.svg b/dist/icons/color/browser/gsa.svg
new file mode 100644
index 000000000..05d397a8c
--- /dev/null
+++ b/dist/icons/color/browser/gsa.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/icecat.png b/dist/icons/color/browser/icecat.png
new file mode 100644
index 000000000..ad1953ae4
Binary files /dev/null and b/dist/icons/color/browser/icecat.png differ
diff --git a/dist/icons/color/browser/ie.svg b/dist/icons/color/browser/ie.svg
new file mode 100644
index 000000000..4ac80b33f
--- /dev/null
+++ b/dist/icons/color/browser/ie.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/konqueror.png b/dist/icons/color/browser/konqueror.png
new file mode 100644
index 000000000..8d2883118
Binary files /dev/null and b/dist/icons/color/browser/konqueror.png differ
diff --git a/dist/icons/color/browser/konqueror.svg b/dist/icons/color/browser/konqueror.svg
new file mode 100644
index 000000000..a65c880e7
--- /dev/null
+++ b/dist/icons/color/browser/konqueror.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/maxthon.png b/dist/icons/color/browser/maxthon.png
new file mode 100644
index 000000000..987020e9f
Binary files /dev/null and b/dist/icons/color/browser/maxthon.png differ
diff --git a/dist/icons/color/browser/midori.png b/dist/icons/color/browser/midori.png
new file mode 100644
index 000000000..681d94faa
Binary files /dev/null and b/dist/icons/color/browser/midori.png differ
diff --git a/dist/icons/color/browser/midori.svg b/dist/icons/color/browser/midori.svg
new file mode 100644
index 000000000..66bc085ab
--- /dev/null
+++ b/dist/icons/color/browser/midori.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/netsurf.png b/dist/icons/color/browser/netsurf.png
new file mode 100644
index 000000000..0e0e3717f
Binary files /dev/null and b/dist/icons/color/browser/netsurf.png differ
diff --git a/dist/icons/color/browser/netsurf.svg b/dist/icons/color/browser/netsurf.svg
new file mode 100644
index 000000000..67aa01bc7
--- /dev/null
+++ b/dist/icons/color/browser/netsurf.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/opera-gx.png b/dist/icons/color/browser/opera-gx.png
new file mode 100644
index 000000000..250cc2529
Binary files /dev/null and b/dist/icons/color/browser/opera-gx.png differ
diff --git a/dist/icons/color/browser/opera-gx.svg b/dist/icons/color/browser/opera-gx.svg
new file mode 100644
index 000000000..aeb51a519
--- /dev/null
+++ b/dist/icons/color/browser/opera-gx.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/opera-mini.png b/dist/icons/color/browser/opera-mini.png
new file mode 100644
index 000000000..bf220034f
Binary files /dev/null and b/dist/icons/color/browser/opera-mini.png differ
diff --git a/dist/icons/color/browser/opera-touch.png b/dist/icons/color/browser/opera-touch.png
new file mode 100644
index 000000000..3beaa36aa
Binary files /dev/null and b/dist/icons/color/browser/opera-touch.png differ
diff --git a/dist/icons/color/browser/opera.png b/dist/icons/color/browser/opera.png
new file mode 100644
index 000000000..472a15234
Binary files /dev/null and b/dist/icons/color/browser/opera.png differ
diff --git a/dist/icons/color/browser/opera.svg b/dist/icons/color/browser/opera.svg
new file mode 100644
index 000000000..6e79822ef
--- /dev/null
+++ b/dist/icons/color/browser/opera.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/palemoon.png b/dist/icons/color/browser/palemoon.png
new file mode 100644
index 000000000..8696c0600
Binary files /dev/null and b/dist/icons/color/browser/palemoon.png differ
diff --git a/dist/icons/color/browser/puffin.png b/dist/icons/color/browser/puffin.png
new file mode 100644
index 000000000..f51149355
Binary files /dev/null and b/dist/icons/color/browser/puffin.png differ
diff --git a/dist/icons/color/browser/safari.png b/dist/icons/color/browser/safari.png
new file mode 100644
index 000000000..6fe6f54f0
Binary files /dev/null and b/dist/icons/color/browser/safari.png differ
diff --git a/dist/icons/color/browser/safari.svg b/dist/icons/color/browser/safari.svg
new file mode 100644
index 000000000..214ccc51a
--- /dev/null
+++ b/dist/icons/color/browser/safari.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/samsung-internet.png b/dist/icons/color/browser/samsung-internet.png
new file mode 100644
index 000000000..543645cbe
Binary files /dev/null and b/dist/icons/color/browser/samsung-internet.png differ
diff --git a/dist/icons/color/browser/samsung-internet.svg b/dist/icons/color/browser/samsung-internet.svg
new file mode 100644
index 000000000..5b2caa93b
--- /dev/null
+++ b/dist/icons/color/browser/samsung-internet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/seamonkey.png b/dist/icons/color/browser/seamonkey.png
new file mode 100644
index 000000000..55929783e
Binary files /dev/null and b/dist/icons/color/browser/seamonkey.png differ
diff --git a/dist/icons/color/browser/silk.png b/dist/icons/color/browser/silk.png
new file mode 100644
index 000000000..161e5c446
Binary files /dev/null and b/dist/icons/color/browser/silk.png differ
diff --git a/dist/icons/color/browser/sogou-mobile.png b/dist/icons/color/browser/sogou-mobile.png
new file mode 100644
index 000000000..33bb21040
Binary files /dev/null and b/dist/icons/color/browser/sogou-mobile.png differ
diff --git a/dist/icons/color/browser/ucbrowser.png b/dist/icons/color/browser/ucbrowser.png
new file mode 100644
index 000000000..a7f0f978f
Binary files /dev/null and b/dist/icons/color/browser/ucbrowser.png differ
diff --git a/dist/icons/color/browser/ucbrowser.svg b/dist/icons/color/browser/ucbrowser.svg
new file mode 100644
index 000000000..cb461fb2a
--- /dev/null
+++ b/dist/icons/color/browser/ucbrowser.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/vivaldi.png b/dist/icons/color/browser/vivaldi.png
new file mode 100644
index 000000000..64f0d32e7
Binary files /dev/null and b/dist/icons/color/browser/vivaldi.png differ
diff --git a/dist/icons/color/browser/vivaldi.svg b/dist/icons/color/browser/vivaldi.svg
new file mode 100644
index 000000000..2084b9758
--- /dev/null
+++ b/dist/icons/color/browser/vivaldi.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/webkit.png b/dist/icons/color/browser/webkit.png
new file mode 100644
index 000000000..cbc0f1b7b
Binary files /dev/null and b/dist/icons/color/browser/webkit.png differ
diff --git a/dist/icons/color/browser/webkit.svg b/dist/icons/color/browser/webkit.svg
new file mode 100644
index 000000000..e1ba7d330
--- /dev/null
+++ b/dist/icons/color/browser/webkit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/color/browser/yandex.png b/dist/icons/color/browser/yandex.png
new file mode 100644
index 000000000..896456d20
Binary files /dev/null and b/dist/icons/color/browser/yandex.png differ
diff --git a/dist/icons/color/os/centos.svg b/dist/icons/color/os/centos.svg
new file mode 100644
index 000000000..2338e1876
--- /dev/null
+++ b/dist/icons/color/os/centos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/mono/LICENSE.md b/dist/icons/mono/LICENSE.md
new file mode 100644
index 000000000..a4c806be3
--- /dev/null
+++ b/dist/icons/mono/LICENSE.md
@@ -0,0 +1,3 @@
+CC0-1.0
+https://github.com/simple-icons/simple-icons
+https://github.com/coreui/coreui-icons
\ No newline at end of file
diff --git a/dist/icons/mono/browser/alipay.svg b/dist/icons/mono/browser/alipay.svg
new file mode 100644
index 000000000..30fa27f22
--- /dev/null
+++ b/dist/icons/mono/browser/alipay.svg
@@ -0,0 +1 @@
+Alipay
\ No newline at end of file
diff --git a/dist/icons/mono/browser/android-browser.svg b/dist/icons/mono/browser/android-browser.svg
new file mode 100644
index 000000000..2482150a6
--- /dev/null
+++ b/dist/icons/mono/browser/android-browser.svg
@@ -0,0 +1 @@
+Android Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/avast-secure-browser.svg b/dist/icons/mono/browser/avast-secure-browser.svg
new file mode 100644
index 000000000..b77658a1a
--- /dev/null
+++ b/dist/icons/mono/browser/avast-secure-browser.svg
@@ -0,0 +1 @@
+Avast Secure Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/baidu-browser.svg b/dist/icons/mono/browser/baidu-browser.svg
new file mode 100644
index 000000000..f7f0dd163
--- /dev/null
+++ b/dist/icons/mono/browser/baidu-browser.svg
@@ -0,0 +1 @@
+Baidu Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/brave.svg b/dist/icons/mono/browser/brave.svg
new file mode 100644
index 000000000..fa798a452
--- /dev/null
+++ b/dist/icons/mono/browser/brave.svg
@@ -0,0 +1 @@
+Brave
\ No newline at end of file
diff --git a/dist/icons/mono/browser/chrome-headless.svg b/dist/icons/mono/browser/chrome-headless.svg
new file mode 100644
index 000000000..5fc57d212
--- /dev/null
+++ b/dist/icons/mono/browser/chrome-headless.svg
@@ -0,0 +1 @@
+Chrome Headless
\ No newline at end of file
diff --git a/dist/icons/mono/browser/chrome-webview.svg b/dist/icons/mono/browser/chrome-webview.svg
new file mode 100644
index 000000000..38b45e32c
--- /dev/null
+++ b/dist/icons/mono/browser/chrome-webview.svg
@@ -0,0 +1 @@
+Chrome Webview
\ No newline at end of file
diff --git a/dist/icons/mono/browser/chrome.svg b/dist/icons/mono/browser/chrome.svg
new file mode 100644
index 000000000..3404d6e21
--- /dev/null
+++ b/dist/icons/mono/browser/chrome.svg
@@ -0,0 +1 @@
+Chrome
\ No newline at end of file
diff --git a/dist/icons/mono/browser/chromium.svg b/dist/icons/mono/browser/chromium.svg
new file mode 100644
index 000000000..90605418c
--- /dev/null
+++ b/dist/icons/mono/browser/chromium.svg
@@ -0,0 +1 @@
+Chromium
\ No newline at end of file
diff --git a/dist/icons/mono/browser/duckduckgo.svg b/dist/icons/mono/browser/duckduckgo.svg
new file mode 100644
index 000000000..2f466f49e
--- /dev/null
+++ b/dist/icons/mono/browser/duckduckgo.svg
@@ -0,0 +1 @@
+DuckDuckGo
\ No newline at end of file
diff --git a/dist/icons/mono/browser/edge.svg b/dist/icons/mono/browser/edge.svg
new file mode 100644
index 000000000..958a7ed0b
--- /dev/null
+++ b/dist/icons/mono/browser/edge.svg
@@ -0,0 +1 @@
+Edge
\ No newline at end of file
diff --git a/dist/icons/mono/browser/electron.svg b/dist/icons/mono/browser/electron.svg
new file mode 100644
index 000000000..8e855ab30
--- /dev/null
+++ b/dist/icons/mono/browser/electron.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/mono/browser/facebook.svg b/dist/icons/mono/browser/facebook.svg
new file mode 100644
index 000000000..f66767e46
--- /dev/null
+++ b/dist/icons/mono/browser/facebook.svg
@@ -0,0 +1 @@
+Facebook
\ No newline at end of file
diff --git a/dist/icons/mono/browser/firefox-focus.svg b/dist/icons/mono/browser/firefox-focus.svg
new file mode 100644
index 000000000..009082ea8
--- /dev/null
+++ b/dist/icons/mono/browser/firefox-focus.svg
@@ -0,0 +1 @@
+Firefox Focus
\ No newline at end of file
diff --git a/dist/icons/mono/browser/firefox-reality.svg b/dist/icons/mono/browser/firefox-reality.svg
new file mode 100644
index 000000000..e02dcdfeb
--- /dev/null
+++ b/dist/icons/mono/browser/firefox-reality.svg
@@ -0,0 +1 @@
+Firefox Reality
\ No newline at end of file
diff --git a/dist/icons/mono/browser/firefox.svg b/dist/icons/mono/browser/firefox.svg
new file mode 100644
index 000000000..baace0bad
--- /dev/null
+++ b/dist/icons/mono/browser/firefox.svg
@@ -0,0 +1 @@
+Firefox
\ No newline at end of file
diff --git a/dist/icons/mono/browser/gsa.svg b/dist/icons/mono/browser/gsa.svg
new file mode 100644
index 000000000..753feb554
--- /dev/null
+++ b/dist/icons/mono/browser/gsa.svg
@@ -0,0 +1 @@
+GSA
\ No newline at end of file
diff --git a/dist/icons/mono/browser/huawei-browser.svg b/dist/icons/mono/browser/huawei-browser.svg
new file mode 100644
index 000000000..200489d3c
--- /dev/null
+++ b/dist/icons/mono/browser/huawei-browser.svg
@@ -0,0 +1 @@
+Huawei Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/icecat.svg b/dist/icons/mono/browser/icecat.svg
new file mode 100644
index 000000000..05b01735f
--- /dev/null
+++ b/dist/icons/mono/browser/icecat.svg
@@ -0,0 +1 @@
+IceCat
\ No newline at end of file
diff --git a/dist/icons/mono/browser/ie.svg b/dist/icons/mono/browser/ie.svg
new file mode 100644
index 000000000..f444f15fc
--- /dev/null
+++ b/dist/icons/mono/browser/ie.svg
@@ -0,0 +1 @@
+IE
\ No newline at end of file
diff --git a/dist/icons/mono/browser/instagram.svg b/dist/icons/mono/browser/instagram.svg
new file mode 100644
index 000000000..c0e86b0bf
--- /dev/null
+++ b/dist/icons/mono/browser/instagram.svg
@@ -0,0 +1 @@
+Instagram
\ No newline at end of file
diff --git a/dist/icons/mono/browser/jasmine.svg b/dist/icons/mono/browser/jasmine.svg
new file mode 100644
index 000000000..bda1b3ed9
--- /dev/null
+++ b/dist/icons/mono/browser/jasmine.svg
@@ -0,0 +1 @@
+Jasmine
\ No newline at end of file
diff --git a/dist/icons/mono/browser/kakaotalk.svg b/dist/icons/mono/browser/kakaotalk.svg
new file mode 100644
index 000000000..8e9f9b123
--- /dev/null
+++ b/dist/icons/mono/browser/kakaotalk.svg
@@ -0,0 +1 @@
+KakaoTalk
\ No newline at end of file
diff --git a/dist/icons/mono/browser/klarna.svg b/dist/icons/mono/browser/klarna.svg
new file mode 100644
index 000000000..c12c2cef4
--- /dev/null
+++ b/dist/icons/mono/browser/klarna.svg
@@ -0,0 +1 @@
+Klarna
\ No newline at end of file
diff --git a/dist/icons/mono/browser/line.svg b/dist/icons/mono/browser/line.svg
new file mode 100644
index 000000000..9e4dc98c2
--- /dev/null
+++ b/dist/icons/mono/browser/line.svg
@@ -0,0 +1 @@
+LINE
\ No newline at end of file
diff --git a/dist/icons/mono/browser/linkedin.svg b/dist/icons/mono/browser/linkedin.svg
new file mode 100644
index 000000000..caa6e69d1
--- /dev/null
+++ b/dist/icons/mono/browser/linkedin.svg
@@ -0,0 +1 @@
+LinkedIn
\ No newline at end of file
diff --git a/dist/icons/mono/browser/miui-browser.svg b/dist/icons/mono/browser/miui-browser.svg
new file mode 100644
index 000000000..a86433446
--- /dev/null
+++ b/dist/icons/mono/browser/miui-browser.svg
@@ -0,0 +1 @@
+MIUI Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/mobile-chrome.svg b/dist/icons/mono/browser/mobile-chrome.svg
new file mode 100644
index 000000000..cc6792673
--- /dev/null
+++ b/dist/icons/mono/browser/mobile-chrome.svg
@@ -0,0 +1 @@
+Mobile Chrome
\ No newline at end of file
diff --git a/dist/icons/mono/browser/mobile-firefox.svg b/dist/icons/mono/browser/mobile-firefox.svg
new file mode 100644
index 000000000..b00ccef29
--- /dev/null
+++ b/dist/icons/mono/browser/mobile-firefox.svg
@@ -0,0 +1 @@
+Mobile Firefox
\ No newline at end of file
diff --git a/dist/icons/mono/browser/mobile-safari.svg b/dist/icons/mono/browser/mobile-safari.svg
new file mode 100644
index 000000000..ac9eab67b
--- /dev/null
+++ b/dist/icons/mono/browser/mobile-safari.svg
@@ -0,0 +1 @@
+Mobile Safari
\ No newline at end of file
diff --git a/dist/icons/mono/browser/mozilla.svg b/dist/icons/mono/browser/mozilla.svg
new file mode 100644
index 000000000..507eb6695
--- /dev/null
+++ b/dist/icons/mono/browser/mozilla.svg
@@ -0,0 +1 @@
+Mozilla
\ No newline at end of file
diff --git a/dist/icons/mono/browser/naver.svg b/dist/icons/mono/browser/naver.svg
new file mode 100644
index 000000000..611ad4b64
--- /dev/null
+++ b/dist/icons/mono/browser/naver.svg
@@ -0,0 +1 @@
+Naver
\ No newline at end of file
diff --git a/dist/icons/mono/browser/nokia-browser.svg b/dist/icons/mono/browser/nokia-browser.svg
new file mode 100644
index 000000000..bf4b85c6b
--- /dev/null
+++ b/dist/icons/mono/browser/nokia-browser.svg
@@ -0,0 +1 @@
+Nokia Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/oculus-browser.svg b/dist/icons/mono/browser/oculus-browser.svg
new file mode 100644
index 000000000..a0d272f06
--- /dev/null
+++ b/dist/icons/mono/browser/oculus-browser.svg
@@ -0,0 +1 @@
+Oculus Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera-coast.svg b/dist/icons/mono/browser/opera-coast.svg
new file mode 100644
index 000000000..f7c88c193
--- /dev/null
+++ b/dist/icons/mono/browser/opera-coast.svg
@@ -0,0 +1 @@
+Opera Coast
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera-gx.svg b/dist/icons/mono/browser/opera-gx.svg
new file mode 100644
index 000000000..b560822cb
--- /dev/null
+++ b/dist/icons/mono/browser/opera-gx.svg
@@ -0,0 +1 @@
+Opera GX
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera-mini.svg b/dist/icons/mono/browser/opera-mini.svg
new file mode 100644
index 000000000..1cc64857a
--- /dev/null
+++ b/dist/icons/mono/browser/opera-mini.svg
@@ -0,0 +1 @@
+Opera Mini
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera-mobi.svg b/dist/icons/mono/browser/opera-mobi.svg
new file mode 100644
index 000000000..dfc102673
--- /dev/null
+++ b/dist/icons/mono/browser/opera-mobi.svg
@@ -0,0 +1 @@
+Opera Mobi
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera-tablet.svg b/dist/icons/mono/browser/opera-tablet.svg
new file mode 100644
index 000000000..d47eea29c
--- /dev/null
+++ b/dist/icons/mono/browser/opera-tablet.svg
@@ -0,0 +1 @@
+Opera Tablet
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera-touch.svg b/dist/icons/mono/browser/opera-touch.svg
new file mode 100644
index 000000000..50211d4de
--- /dev/null
+++ b/dist/icons/mono/browser/opera-touch.svg
@@ -0,0 +1 @@
+Opera Touch
\ No newline at end of file
diff --git a/dist/icons/mono/browser/opera.svg b/dist/icons/mono/browser/opera.svg
new file mode 100644
index 000000000..d1194ee24
--- /dev/null
+++ b/dist/icons/mono/browser/opera.svg
@@ -0,0 +1 @@
+Opera
\ No newline at end of file
diff --git a/dist/icons/mono/browser/qqbrowser.svg b/dist/icons/mono/browser/qqbrowser.svg
new file mode 100644
index 000000000..6a94a49f7
--- /dev/null
+++ b/dist/icons/mono/browser/qqbrowser.svg
@@ -0,0 +1 @@
+QQBrowser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/qqbrowserlite.svg b/dist/icons/mono/browser/qqbrowserlite.svg
new file mode 100644
index 000000000..d4ccef286
--- /dev/null
+++ b/dist/icons/mono/browser/qqbrowserlite.svg
@@ -0,0 +1 @@
+QQBrowserLite
\ No newline at end of file
diff --git a/dist/icons/mono/browser/safari.svg b/dist/icons/mono/browser/safari.svg
new file mode 100644
index 000000000..5d1543889
--- /dev/null
+++ b/dist/icons/mono/browser/safari.svg
@@ -0,0 +1 @@
+Safari
\ No newline at end of file
diff --git a/dist/icons/mono/browser/sailfish-browser.svg b/dist/icons/mono/browser/sailfish-browser.svg
new file mode 100644
index 000000000..0884da1c3
--- /dev/null
+++ b/dist/icons/mono/browser/sailfish-browser.svg
@@ -0,0 +1 @@
+Sailfish Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/samsung-internet.svg b/dist/icons/mono/browser/samsung-internet.svg
new file mode 100644
index 000000000..15e89529f
--- /dev/null
+++ b/dist/icons/mono/browser/samsung-internet.svg
@@ -0,0 +1 @@
+Samsung Internet
\ No newline at end of file
diff --git a/dist/icons/mono/browser/smart-lenovo-browser.svg b/dist/icons/mono/browser/smart-lenovo-browser.svg
new file mode 100644
index 000000000..e9be1500a
--- /dev/null
+++ b/dist/icons/mono/browser/smart-lenovo-browser.svg
@@ -0,0 +1 @@
+Smart Lenovo Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/snapchat.svg b/dist/icons/mono/browser/snapchat.svg
new file mode 100644
index 000000000..3110fbada
--- /dev/null
+++ b/dist/icons/mono/browser/snapchat.svg
@@ -0,0 +1 @@
+Snapchat
\ No newline at end of file
diff --git a/dist/icons/mono/browser/sogou-explorer.svg b/dist/icons/mono/browser/sogou-explorer.svg
new file mode 100644
index 000000000..1449ecb61
--- /dev/null
+++ b/dist/icons/mono/browser/sogou-explorer.svg
@@ -0,0 +1 @@
+Sogou Explorer
\ No newline at end of file
diff --git a/dist/icons/mono/browser/sogou-mobile.svg b/dist/icons/mono/browser/sogou-mobile.svg
new file mode 100644
index 000000000..36165fe4c
--- /dev/null
+++ b/dist/icons/mono/browser/sogou-mobile.svg
@@ -0,0 +1 @@
+Sogou Mobile
\ No newline at end of file
diff --git a/dist/icons/mono/browser/tesla.svg b/dist/icons/mono/browser/tesla.svg
new file mode 100644
index 000000000..77e66012d
--- /dev/null
+++ b/dist/icons/mono/browser/tesla.svg
@@ -0,0 +1 @@
+Tesla
\ No newline at end of file
diff --git a/dist/icons/mono/browser/tiktok.svg b/dist/icons/mono/browser/tiktok.svg
new file mode 100644
index 000000000..57ce3ae5d
--- /dev/null
+++ b/dist/icons/mono/browser/tiktok.svg
@@ -0,0 +1 @@
+TikTok
\ No newline at end of file
diff --git a/dist/icons/mono/browser/twitter.svg b/dist/icons/mono/browser/twitter.svg
new file mode 100644
index 000000000..b4aeefd37
--- /dev/null
+++ b/dist/icons/mono/browser/twitter.svg
@@ -0,0 +1 @@
+Twitter
\ No newline at end of file
diff --git a/dist/icons/mono/browser/vivaldi.svg b/dist/icons/mono/browser/vivaldi.svg
new file mode 100644
index 000000000..167e8ad69
--- /dev/null
+++ b/dist/icons/mono/browser/vivaldi.svg
@@ -0,0 +1 @@
+Vivaldi
\ No newline at end of file
diff --git a/dist/icons/mono/browser/vivo-browser.svg b/dist/icons/mono/browser/vivo-browser.svg
new file mode 100644
index 000000000..db5270682
--- /dev/null
+++ b/dist/icons/mono/browser/vivo-browser.svg
@@ -0,0 +1 @@
+vivo Browser
\ No newline at end of file
diff --git a/dist/icons/mono/browser/wechat.svg b/dist/icons/mono/browser/wechat.svg
new file mode 100644
index 000000000..c3eb6c4a6
--- /dev/null
+++ b/dist/icons/mono/browser/wechat.svg
@@ -0,0 +1 @@
+WeChat
\ No newline at end of file
diff --git a/dist/icons/mono/browser/weibo.svg b/dist/icons/mono/browser/weibo.svg
new file mode 100644
index 000000000..13944085e
--- /dev/null
+++ b/dist/icons/mono/browser/weibo.svg
@@ -0,0 +1 @@
+Weibo
\ No newline at end of file
diff --git a/dist/icons/mono/browser/yandex.svg b/dist/icons/mono/browser/yandex.svg
new file mode 100644
index 000000000..16a76fe16
--- /dev/null
+++ b/dist/icons/mono/browser/yandex.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/mono/device/acer.svg b/dist/icons/mono/device/acer.svg
new file mode 100644
index 000000000..ca9b2bbf7
--- /dev/null
+++ b/dist/icons/mono/device/acer.svg
@@ -0,0 +1 @@
+Acer
\ No newline at end of file
diff --git a/dist/icons/mono/device/amazon.svg b/dist/icons/mono/device/amazon.svg
new file mode 100644
index 000000000..70316babd
--- /dev/null
+++ b/dist/icons/mono/device/amazon.svg
@@ -0,0 +1 @@
+Amazon
\ No newline at end of file
diff --git a/dist/icons/mono/device/apple.svg b/dist/icons/mono/device/apple.svg
new file mode 100644
index 000000000..de4b0d17b
--- /dev/null
+++ b/dist/icons/mono/device/apple.svg
@@ -0,0 +1 @@
+Apple
\ No newline at end of file
diff --git a/dist/icons/mono/device/asus.svg b/dist/icons/mono/device/asus.svg
new file mode 100644
index 000000000..18ef199da
--- /dev/null
+++ b/dist/icons/mono/device/asus.svg
@@ -0,0 +1 @@
+ASUS
\ No newline at end of file
diff --git a/dist/icons/mono/device/att.svg b/dist/icons/mono/device/att.svg
new file mode 100644
index 000000000..166ca3920
--- /dev/null
+++ b/dist/icons/mono/device/att.svg
@@ -0,0 +1 @@
+AT&T
\ No newline at end of file
diff --git a/dist/icons/mono/device/blackberry.svg b/dist/icons/mono/device/blackberry.svg
new file mode 100644
index 000000000..b0c55837a
--- /dev/null
+++ b/dist/icons/mono/device/blackberry.svg
@@ -0,0 +1 @@
+Blackberry
\ No newline at end of file
diff --git a/dist/icons/mono/device/dell.svg b/dist/icons/mono/device/dell.svg
new file mode 100644
index 000000000..d272bbf6f
--- /dev/null
+++ b/dist/icons/mono/device/dell.svg
@@ -0,0 +1 @@
+Dell
\ No newline at end of file
diff --git a/dist/icons/mono/device/facebook.svg b/dist/icons/mono/device/facebook.svg
new file mode 100644
index 000000000..f66767e46
--- /dev/null
+++ b/dist/icons/mono/device/facebook.svg
@@ -0,0 +1 @@
+Facebook
\ No newline at end of file
diff --git a/dist/icons/mono/device/fairphone.svg b/dist/icons/mono/device/fairphone.svg
new file mode 100644
index 000000000..3262e30dc
--- /dev/null
+++ b/dist/icons/mono/device/fairphone.svg
@@ -0,0 +1 @@
+Fairphone
\ No newline at end of file
diff --git a/dist/icons/mono/device/google.svg b/dist/icons/mono/device/google.svg
new file mode 100644
index 000000000..2eaf91554
--- /dev/null
+++ b/dist/icons/mono/device/google.svg
@@ -0,0 +1 @@
+Google
\ No newline at end of file
diff --git a/dist/icons/mono/device/hp.svg b/dist/icons/mono/device/hp.svg
new file mode 100644
index 000000000..04b5f0ef8
--- /dev/null
+++ b/dist/icons/mono/device/hp.svg
@@ -0,0 +1 @@
+HP
\ No newline at end of file
diff --git a/dist/icons/mono/device/huawei.svg b/dist/icons/mono/device/huawei.svg
new file mode 100644
index 000000000..0c72bab82
--- /dev/null
+++ b/dist/icons/mono/device/huawei.svg
@@ -0,0 +1 @@
+Huawei
\ No newline at end of file
diff --git a/dist/icons/mono/device/lenovo.svg b/dist/icons/mono/device/lenovo.svg
new file mode 100644
index 000000000..c03719a43
--- /dev/null
+++ b/dist/icons/mono/device/lenovo.svg
@@ -0,0 +1 @@
+Lenovo
\ No newline at end of file
diff --git a/dist/icons/mono/device/lg.svg b/dist/icons/mono/device/lg.svg
new file mode 100644
index 000000000..5eedc7978
--- /dev/null
+++ b/dist/icons/mono/device/lg.svg
@@ -0,0 +1 @@
+LG
\ No newline at end of file
diff --git a/dist/icons/mono/device/meizu.svg b/dist/icons/mono/device/meizu.svg
new file mode 100644
index 000000000..8f903f6dc
--- /dev/null
+++ b/dist/icons/mono/device/meizu.svg
@@ -0,0 +1 @@
+Meizu
\ No newline at end of file
diff --git a/dist/icons/mono/device/microsoft.svg b/dist/icons/mono/device/microsoft.svg
new file mode 100644
index 000000000..eeacf2520
--- /dev/null
+++ b/dist/icons/mono/device/microsoft.svg
@@ -0,0 +1 @@
+Microsoft
\ No newline at end of file
diff --git a/dist/icons/mono/device/motorola.svg b/dist/icons/mono/device/motorola.svg
new file mode 100644
index 000000000..3e744bda2
--- /dev/null
+++ b/dist/icons/mono/device/motorola.svg
@@ -0,0 +1 @@
+Motorola
\ No newline at end of file
diff --git a/dist/icons/mono/device/nintendo.svg b/dist/icons/mono/device/nintendo.svg
new file mode 100644
index 000000000..5cad9642f
--- /dev/null
+++ b/dist/icons/mono/device/nintendo.svg
@@ -0,0 +1 @@
+Nintendo
\ No newline at end of file
diff --git a/dist/icons/mono/device/nokia.svg b/dist/icons/mono/device/nokia.svg
new file mode 100644
index 000000000..a4a21137e
--- /dev/null
+++ b/dist/icons/mono/device/nokia.svg
@@ -0,0 +1 @@
+Nokia
\ No newline at end of file
diff --git a/dist/icons/mono/device/nvidia.svg b/dist/icons/mono/device/nvidia.svg
new file mode 100644
index 000000000..2c7ff66f8
--- /dev/null
+++ b/dist/icons/mono/device/nvidia.svg
@@ -0,0 +1 @@
+NVIDIA
\ No newline at end of file
diff --git a/dist/icons/mono/device/oneplus.svg b/dist/icons/mono/device/oneplus.svg
new file mode 100644
index 000000000..11c3131ad
--- /dev/null
+++ b/dist/icons/mono/device/oneplus.svg
@@ -0,0 +1 @@
+OnePlus
\ No newline at end of file
diff --git a/dist/icons/mono/device/oppo.svg b/dist/icons/mono/device/oppo.svg
new file mode 100644
index 000000000..b886af3e7
--- /dev/null
+++ b/dist/icons/mono/device/oppo.svg
@@ -0,0 +1 @@
+OPPO
\ No newline at end of file
diff --git a/dist/icons/mono/device/panasonic.svg b/dist/icons/mono/device/panasonic.svg
new file mode 100644
index 000000000..e5902436c
--- /dev/null
+++ b/dist/icons/mono/device/panasonic.svg
@@ -0,0 +1 @@
+Panasonic
\ No newline at end of file
diff --git a/dist/icons/mono/device/roku.svg b/dist/icons/mono/device/roku.svg
new file mode 100644
index 000000000..17dde5e36
--- /dev/null
+++ b/dist/icons/mono/device/roku.svg
@@ -0,0 +1 @@
+Roku
\ No newline at end of file
diff --git a/dist/icons/mono/device/samsung.svg b/dist/icons/mono/device/samsung.svg
new file mode 100644
index 000000000..627cbf2c1
--- /dev/null
+++ b/dist/icons/mono/device/samsung.svg
@@ -0,0 +1 @@
+Samsung
\ No newline at end of file
diff --git a/dist/icons/mono/device/siemens.svg b/dist/icons/mono/device/siemens.svg
new file mode 100644
index 000000000..ec60e7b17
--- /dev/null
+++ b/dist/icons/mono/device/siemens.svg
@@ -0,0 +1 @@
+Siemens
\ No newline at end of file
diff --git a/dist/icons/mono/device/sony.svg b/dist/icons/mono/device/sony.svg
new file mode 100644
index 000000000..38d17e13d
--- /dev/null
+++ b/dist/icons/mono/device/sony.svg
@@ -0,0 +1 @@
+Sony
\ No newline at end of file
diff --git a/dist/icons/mono/device/tesla.svg b/dist/icons/mono/device/tesla.svg
new file mode 100644
index 000000000..77e66012d
--- /dev/null
+++ b/dist/icons/mono/device/tesla.svg
@@ -0,0 +1 @@
+Tesla
\ No newline at end of file
diff --git a/dist/icons/mono/device/vivo.svg b/dist/icons/mono/device/vivo.svg
new file mode 100644
index 000000000..0591ac425
--- /dev/null
+++ b/dist/icons/mono/device/vivo.svg
@@ -0,0 +1 @@
+vivo
\ No newline at end of file
diff --git a/dist/icons/mono/device/vodafone.svg b/dist/icons/mono/device/vodafone.svg
new file mode 100644
index 000000000..f882e2fe0
--- /dev/null
+++ b/dist/icons/mono/device/vodafone.svg
@@ -0,0 +1 @@
+Vodafone
\ No newline at end of file
diff --git a/dist/icons/mono/device/xiaomi.svg b/dist/icons/mono/device/xiaomi.svg
new file mode 100644
index 000000000..f08eae5bd
--- /dev/null
+++ b/dist/icons/mono/device/xiaomi.svg
@@ -0,0 +1 @@
+Xiaomi
\ No newline at end of file
diff --git a/dist/icons/mono/device/zebra.svg b/dist/icons/mono/device/zebra.svg
new file mode 100644
index 000000000..8282e7744
--- /dev/null
+++ b/dist/icons/mono/device/zebra.svg
@@ -0,0 +1 @@
+Zebra
\ No newline at end of file
diff --git a/dist/icons/mono/os/android.svg b/dist/icons/mono/os/android.svg
new file mode 100644
index 000000000..3f44ef6ba
--- /dev/null
+++ b/dist/icons/mono/os/android.svg
@@ -0,0 +1 @@
+Android
\ No newline at end of file
diff --git a/dist/icons/mono/os/arch.svg b/dist/icons/mono/os/arch.svg
new file mode 100644
index 000000000..a331ef91d
--- /dev/null
+++ b/dist/icons/mono/os/arch.svg
@@ -0,0 +1 @@
+Arch
\ No newline at end of file
diff --git a/dist/icons/mono/os/blackberry.svg b/dist/icons/mono/os/blackberry.svg
new file mode 100644
index 000000000..b0c55837a
--- /dev/null
+++ b/dist/icons/mono/os/blackberry.svg
@@ -0,0 +1 @@
+Blackberry
\ No newline at end of file
diff --git a/dist/icons/mono/os/centos.svg b/dist/icons/mono/os/centos.svg
new file mode 100644
index 000000000..3f16a9e00
--- /dev/null
+++ b/dist/icons/mono/os/centos.svg
@@ -0,0 +1 @@
+CentOS
\ No newline at end of file
diff --git a/dist/icons/mono/os/chrome-os.svg b/dist/icons/mono/os/chrome-os.svg
new file mode 100644
index 000000000..0a282625e
--- /dev/null
+++ b/dist/icons/mono/os/chrome-os.svg
@@ -0,0 +1 @@
+Chrome OS
\ No newline at end of file
diff --git a/dist/icons/mono/os/chromecast.svg b/dist/icons/mono/os/chromecast.svg
new file mode 100644
index 000000000..6b251ed8a
--- /dev/null
+++ b/dist/icons/mono/os/chromecast.svg
@@ -0,0 +1 @@
+Chromecast
\ No newline at end of file
diff --git a/dist/icons/mono/os/debian.svg b/dist/icons/mono/os/debian.svg
new file mode 100644
index 000000000..c983b3245
--- /dev/null
+++ b/dist/icons/mono/os/debian.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/mono/os/deepin.svg b/dist/icons/mono/os/deepin.svg
new file mode 100644
index 000000000..2a5fbca2d
--- /dev/null
+++ b/dist/icons/mono/os/deepin.svg
@@ -0,0 +1 @@
+deepin
\ No newline at end of file
diff --git a/dist/icons/mono/os/elementary-os.svg b/dist/icons/mono/os/elementary-os.svg
new file mode 100644
index 000000000..2b4c9515b
--- /dev/null
+++ b/dist/icons/mono/os/elementary-os.svg
@@ -0,0 +1 @@
+elementary OS
\ No newline at end of file
diff --git a/dist/icons/mono/os/fedora.svg b/dist/icons/mono/os/fedora.svg
new file mode 100644
index 000000000..7f461d0e6
--- /dev/null
+++ b/dist/icons/mono/os/fedora.svg
@@ -0,0 +1 @@
+Fedora
\ No newline at end of file
diff --git a/dist/icons/mono/os/firefox.svg b/dist/icons/mono/os/firefox.svg
new file mode 100644
index 000000000..e7c9fff23
--- /dev/null
+++ b/dist/icons/mono/os/firefox.svg
@@ -0,0 +1 @@
+Firefox
\ No newline at end of file
diff --git a/dist/icons/mono/os/freebsd.svg b/dist/icons/mono/os/freebsd.svg
new file mode 100644
index 000000000..8febe9d4c
--- /dev/null
+++ b/dist/icons/mono/os/freebsd.svg
@@ -0,0 +1 @@
+FreeBSD
\ No newline at end of file
diff --git a/dist/icons/mono/os/gentoo.svg b/dist/icons/mono/os/gentoo.svg
new file mode 100644
index 000000000..4c48a0227
--- /dev/null
+++ b/dist/icons/mono/os/gentoo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/mono/os/gnu.svg b/dist/icons/mono/os/gnu.svg
new file mode 100644
index 000000000..f29f4d7e4
--- /dev/null
+++ b/dist/icons/mono/os/gnu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dist/icons/mono/os/harmonyos.svg b/dist/icons/mono/os/harmonyos.svg
new file mode 100644
index 000000000..99b8d7e62
--- /dev/null
+++ b/dist/icons/mono/os/harmonyos.svg
@@ -0,0 +1 @@
+HarmonyOS
\ No newline at end of file
diff --git a/dist/icons/mono/os/ios.svg b/dist/icons/mono/os/ios.svg
new file mode 100644
index 000000000..a16369768
--- /dev/null
+++ b/dist/icons/mono/os/ios.svg
@@ -0,0 +1 @@
+iOS
\ No newline at end of file
diff --git a/dist/icons/mono/os/kaios.svg b/dist/icons/mono/os/kaios.svg
new file mode 100644
index 000000000..27a0ec770
--- /dev/null
+++ b/dist/icons/mono/os/kaios.svg
@@ -0,0 +1 @@
+KaiOS
\ No newline at end of file
diff --git a/dist/icons/mono/os/linux.svg b/dist/icons/mono/os/linux.svg
new file mode 100644
index 000000000..381d3d8a8
--- /dev/null
+++ b/dist/icons/mono/os/linux.svg
@@ -0,0 +1 @@
+Linux
\ No newline at end of file
diff --git a/dist/icons/mono/os/macos.svg b/dist/icons/mono/os/macos.svg
new file mode 100644
index 000000000..87630c456
--- /dev/null
+++ b/dist/icons/mono/os/macos.svg
@@ -0,0 +1 @@
+macOS
\ No newline at end of file
diff --git a/dist/icons/mono/os/manjaro.svg b/dist/icons/mono/os/manjaro.svg
new file mode 100644
index 000000000..d99972e2a
--- /dev/null
+++ b/dist/icons/mono/os/manjaro.svg
@@ -0,0 +1 @@
+Manjaro
\ No newline at end of file
diff --git a/dist/icons/mono/os/mint.svg b/dist/icons/mono/os/mint.svg
new file mode 100644
index 000000000..442c8b2d5
--- /dev/null
+++ b/dist/icons/mono/os/mint.svg
@@ -0,0 +1 @@
+Mint
\ No newline at end of file
diff --git a/dist/icons/mono/os/netbsd.svg b/dist/icons/mono/os/netbsd.svg
new file mode 100644
index 000000000..511f066ed
--- /dev/null
+++ b/dist/icons/mono/os/netbsd.svg
@@ -0,0 +1 @@
+NetBSD
\ No newline at end of file
diff --git a/dist/icons/mono/os/nintendo.svg b/dist/icons/mono/os/nintendo.svg
new file mode 100644
index 000000000..5cad9642f
--- /dev/null
+++ b/dist/icons/mono/os/nintendo.svg
@@ -0,0 +1 @@
+Nintendo
\ No newline at end of file
diff --git a/dist/icons/mono/os/openbsd.svg b/dist/icons/mono/os/openbsd.svg
new file mode 100644
index 000000000..e78fbc7e6
--- /dev/null
+++ b/dist/icons/mono/os/openbsd.svg
@@ -0,0 +1 @@
+OpenBSD
\ No newline at end of file
diff --git a/dist/icons/mono/os/playstation.svg b/dist/icons/mono/os/playstation.svg
new file mode 100644
index 000000000..be3370316
--- /dev/null
+++ b/dist/icons/mono/os/playstation.svg
@@ -0,0 +1 @@
+PlayStation
\ No newline at end of file
diff --git a/dist/icons/mono/os/raspbian.svg b/dist/icons/mono/os/raspbian.svg
new file mode 100644
index 000000000..de6feed52
--- /dev/null
+++ b/dist/icons/mono/os/raspbian.svg
@@ -0,0 +1 @@
+Raspbian
\ No newline at end of file
diff --git a/dist/icons/mono/os/redhat.svg b/dist/icons/mono/os/redhat.svg
new file mode 100644
index 000000000..b68e41333
--- /dev/null
+++ b/dist/icons/mono/os/redhat.svg
@@ -0,0 +1 @@
+RedHat
\ No newline at end of file
diff --git a/dist/icons/mono/os/sailfish.svg b/dist/icons/mono/os/sailfish.svg
new file mode 100644
index 000000000..b695e10b4
--- /dev/null
+++ b/dist/icons/mono/os/sailfish.svg
@@ -0,0 +1 @@
+Sailfish
\ No newline at end of file
diff --git a/dist/icons/mono/os/slackware.svg b/dist/icons/mono/os/slackware.svg
new file mode 100644
index 000000000..611bf809d
--- /dev/null
+++ b/dist/icons/mono/os/slackware.svg
@@ -0,0 +1 @@
+Slackware
\ No newline at end of file
diff --git a/dist/icons/mono/os/suse.svg b/dist/icons/mono/os/suse.svg
new file mode 100644
index 000000000..a4e474a1d
--- /dev/null
+++ b/dist/icons/mono/os/suse.svg
@@ -0,0 +1 @@
+SUSE
\ No newline at end of file
diff --git a/dist/icons/mono/os/ubuntu.svg b/dist/icons/mono/os/ubuntu.svg
new file mode 100644
index 000000000..227f86aa3
--- /dev/null
+++ b/dist/icons/mono/os/ubuntu.svg
@@ -0,0 +1 @@
+Ubuntu
\ No newline at end of file
diff --git a/dist/icons/mono/os/windows.svg b/dist/icons/mono/os/windows.svg
new file mode 100644
index 000000000..9c79dffdb
--- /dev/null
+++ b/dist/icons/mono/os/windows.svg
@@ -0,0 +1 @@
+Windows 10
\ No newline at end of file
diff --git a/dist/icons/mono/os/xbox.svg b/dist/icons/mono/os/xbox.svg
new file mode 100644
index 000000000..85a9689a6
--- /dev/null
+++ b/dist/icons/mono/os/xbox.svg
@@ -0,0 +1 @@
+Xbox
\ No newline at end of file
diff --git a/dist/ua-parser.min.js b/dist/ua-parser.min.js
index 3089db6ca..22782dfc5 100644
--- a/dist/ua-parser.min.js
+++ b/dist/ua-parser.min.js
@@ -1,4 +1,4 @@
-/* UAParser.js v0.7.33
- Copyright Š 2012-2021 Faisal Salman
- MIT License */
-(function(window,undefined){"use strict";var LIBVERSION="0.7.33",EMPTY="",UNKNOWN="?",FUNC_TYPE="function",UNDEF_TYPE="undefined",OBJ_TYPE="object",STR_TYPE="string",MAJOR="major",MODEL="model",NAME="name",TYPE="type",VENDOR="vendor",VERSION="version",ARCHITECTURE="architecture",CONSOLE="console",MOBILE="mobile",TABLET="tablet",SMARTTV="smarttv",WEARABLE="wearable",EMBEDDED="embedded",UA_MAX_LENGTH=350;var AMAZON="Amazon",APPLE="Apple",ASUS="ASUS",BLACKBERRY="BlackBerry",BROWSER="Browser",CHROME="Chrome",EDGE="Edge",FIREFOX="Firefox",GOOGLE="Google",HUAWEI="Huawei",LG="LG",MICROSOFT="Microsoft",MOTOROLA="Motorola",OPERA="Opera",SAMSUNG="Samsung",SHARP="Sharp",SONY="Sony",XIAOMI="Xiaomi",ZEBRA="Zebra",FACEBOOK="Facebook";var extend=function(regexes,extensions){var mergedRegexes={};for(var i in regexes){if(extensions[i]&&extensions[i].length%2===0){mergedRegexes[i]=extensions[i].concat(regexes[i])}else{mergedRegexes[i]=regexes[i]}}return mergedRegexes},enumerize=function(arr){var enums={};for(var i=0;i0){if(q.length===2){if(typeof q[1]==FUNC_TYPE){this[q[0]]=q[1].call(this,match)}else{this[q[0]]=q[1]}}else if(q.length===3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){this[q[0]]=match?q[1].call(this,match,q[2]):undefined}else{this[q[0]]=match?match.replace(q[1],q[2]):undefined}}else if(q.length===4){this[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}}else{this[q]=match?match:undefined}}}}i+=2}},strMapper=function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;jUA_MAX_LENGTH?trim(ua,UA_MAX_LENGTH):ua;return this};this.setUA(_ua);return this};UAParser.VERSION=LIBVERSION;UAParser.BROWSER=enumerize([NAME,VERSION,MAJOR]);UAParser.CPU=enumerize([ARCHITECTURE]);UAParser.DEVICE=enumerize([MODEL,VENDOR,TYPE,CONSOLE,MOBILE,SMARTTV,TABLET,WEARABLE,EMBEDDED]);UAParser.ENGINE=UAParser.OS=enumerize([NAME,VERSION]);if(typeof exports!==UNDEF_TYPE){if(typeof module!==UNDEF_TYPE&&module.exports){exports=module.exports=UAParser}exports.UAParser=UAParser}else{if(typeof define===FUNC_TYPE&&define.amd){define(function(){return UAParser})}else if(typeof window!==UNDEF_TYPE){window.UAParser=UAParser}}var $=typeof window!==UNDEF_TYPE&&(window.jQuery||window.Zepto);if($&&!$.ua){var parser=new UAParser;$.ua=parser.getResult();$.ua.get=function(){return parser.getUA()};$.ua.set=function(ua){parser.setUA(ua);var result=parser.getResult();for(var prop in result){$.ua[prop]=result[prop]}}}})(typeof window==="object"?window:this);
\ No newline at end of file
+/* UAParser.js v2.0.4
+ Copyright Š 2012-2025 Faisal Salman
+ AGPLv3 License */
+(function(window,undefined){"use strict";var LIBVERSION="2.0.4",UA_MAX_LENGTH=500,USER_AGENT="user-agent",EMPTY="",UNKNOWN="?",FUNC_TYPE="function",UNDEF_TYPE="undefined",OBJ_TYPE="object",STR_TYPE="string",UA_BROWSER="browser",UA_CPU="cpu",UA_DEVICE="device",UA_ENGINE="engine",UA_OS="os",UA_RESULT="result",NAME="name",TYPE="type",VENDOR="vendor",VERSION="version",ARCHITECTURE="architecture",MAJOR="major",MODEL="model",CONSOLE="console",MOBILE="mobile",TABLET="tablet",SMARTTV="smarttv",WEARABLE="wearable",XR="xr",EMBEDDED="embedded",INAPP="inapp",BRANDS="brands",FORMFACTORS="formFactors",FULLVERLIST="fullVersionList",PLATFORM="platform",PLATFORMVER="platformVersion",BITNESS="bitness",CH_HEADER="sec-ch-ua",CH_HEADER_FULL_VER_LIST=CH_HEADER+"-full-version-list",CH_HEADER_ARCH=CH_HEADER+"-arch",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],AMAZON="Amazon",APPLE="Apple",ASUS="ASUS",BLACKBERRY="BlackBerry",GOOGLE="Google",HUAWEI="Huawei",LENOVO="Lenovo",HONOR="Honor",LG="LG",MICROSOFT="Microsoft",MOTOROLA="Motorola",NVIDIA="Nvidia",ONEPLUS="OnePlus",OPPO="OPPO",SAMSUNG="Samsung",SHARP="Sharp",SONY="Sony",XIAOMI="Xiaomi",ZEBRA="Zebra",CHROME="Chrome",CHROMIUM="Chromium",CHROMECAST="Chromecast",EDGE="Edge",FIREFOX="Firefox",OPERA="Opera",FACEBOOK="Facebook",SOGOU="Sogou",PREFIX_MOBILE="Mobile ",SUFFIX_BROWSER=" Browser",WINDOWS="Windows";var isWindow=typeof window!==UNDEF_TYPE,NAVIGATOR=isWindow&&window.navigator?window.navigator:undefined,NAVIGATOR_UADATA=NAVIGATOR&&NAVIGATOR.userAgentData?NAVIGATOR.userAgentData:undefined;var extend=function(defaultRgx,extensions){var mergedRgx={};var extraRgx=extensions;if(!isExtensions(extensions)){extraRgx={};for(var i in extensions){for(var j in extensions[i]){extraRgx[j]=extensions[i][j].concat(extraRgx[j]?extraRgx[j]:[])}}}for(var k in defaultRgx){mergedRgx[k]=extraRgx[k]&&extraRgx[k].length%2===0?extraRgx[k].concat(defaultRgx[k]):defaultRgx[k]}return mergedRgx},enumerize=function(arr){var enums={};for(var i=0;i0){for(var i in str1){if(lowerize(str2)==lowerize(str1[i]))return true}return false}return isString(str1)?lowerize(str2)==lowerize(str1):false},isExtensions=function(obj,deep){for(var prop in obj){return/^(browser|cpu|device|engine|os)$/.test(prop)||(deep?isExtensions(obj[prop]):false)}},isString=function(val){return typeof val===STR_TYPE},itemListToArray=function(header){if(!header)return undefined;var arr=[];var tokens=strip(/\\?\"/g,header).split(",");for(var i=0;i-1){var token=trim(tokens[i]).split(";v=");arr[i]={brand:token[0],version:token[1]}}else{arr[i]=trim(tokens[i])}}return arr},lowerize=function(str){return isString(str)?str.toLowerCase():str},majorize=function(version){return isString(version)?strip(/[^\d\.]/g,version).split(".")[0]:undefined},setProps=function(arr){for(var i in arr){if(!arr.hasOwnProperty(i))continue;var propName=arr[i];if(typeof propName==OBJ_TYPE&&propName.length==2){this[propName[0]]=propName[1]}else{this[propName]=undefined}}return this},strip=function(pattern,str){return isString(str)?str.replace(pattern,EMPTY):str},stripQuotes=function(str){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)}};var rgxMapper=function(ua,arrays){if(!ua||!arrays)return;var i=0,j,k,p,q,matches,match;while(i0){if(q.length===2){if(typeof q[1]==FUNC_TYPE){this[q[0]]=q[1].call(this,match)}else{this[q[0]]=q[1]}}else if(q.length>=3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){if(q.length>3){this[q[0]]=match?q[1].apply(this,q.slice(2)):undefined}else{this[q[0]]=match?q[1].call(this,match,q[2]):undefined}}else{if(q.length==3){this[q[0]]=match?match.replace(q[1],q[2]):undefined}else if(q.length==4){this[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}else if(q.length>4){this[q[0]]=match?q[3].apply(this,[match.replace(q[1],q[2])].concat(q.slice(4))):undefined}}}}else{this[q]=match?match:undefined}}}}i+=2}},strMapper=function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;j2){this.set(MODEL,"iPad").set(TYPE,TABLET)}break;case UA_OS: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=13?"11":"10";this.set(NAME,osName).set(VERSION,osVersion)}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],["uaCH",uaCH],["rgxMap",rgxMap],["data",createIData(this,itemType)]]);return this}function UAParser(ua,extensions,headers){if(typeof ua===OBJ_TYPE){if(isExtensions(ua,true)){if(typeof extensions===OBJ_TYPE){headers=extensions}extensions=ua}else{headers=ua;extensions=undefined}ua=undefined}else if(typeof ua===STR_TYPE&&!isExtensions(extensions,true)){headers=extensions;extensions=undefined}if(headers&&typeof headers.append===FUNC_TYPE){var kv={};headers.forEach(function(v,k){kv[k]=v});headers=kv}if(!(this instanceof UAParser)){return new UAParser(ua,extensions,headers).getResult()}var userAgent=typeof ua===STR_TYPE?ua:headers&&headers[USER_AGENT]?headers[USER_AGENT]:NAVIGATOR&&NAVIGATOR.userAgent?NAVIGATOR.userAgent:EMPTY,httpUACH=new UACHData(headers,true),regexMap=extensions?extend(defaultRegexes,extensions):defaultRegexes,createItemFunc=function(itemType){if(itemType==UA_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()).get()}}else{return function(){return new UAItem(itemType,userAgent,regexMap[itemType],httpUACH).parseUA().get()}}};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)],["getUA",function(){return userAgent}],["setUA",function(ua){if(isString(ua))userAgent=ua.length>UA_MAX_LENGTH?trim(ua,UA_MAX_LENGTH):ua;return this}]]).setUA(userAgent);return this}UAParser.VERSION=LIBVERSION;UAParser.BROWSER=enumerize([NAME,VERSION,MAJOR,TYPE]);UAParser.CPU=enumerize([ARCHITECTURE]);UAParser.DEVICE=enumerize([MODEL,VENDOR,TYPE,CONSOLE,MOBILE,SMARTTV,TABLET,WEARABLE,EMBEDDED]);UAParser.ENGINE=UAParser.OS=enumerize([NAME,VERSION]);if(typeof exports!==UNDEF_TYPE){if(typeof module!==UNDEF_TYPE&&module.exports){exports=module.exports=UAParser}exports.UAParser=UAParser}else{if(typeof define===FUNC_TYPE&&define.amd){define(function(){return UAParser})}else if(isWindow){window.UAParser=UAParser}}var $=isWindow&&(window.jQuery||window.Zepto);if($&&!$.ua){var parser=new UAParser;$.ua=parser.getResult();$.ua.get=function(){return parser.getUA()};$.ua.set=function(ua){parser.setUA(ua);var result=parser.getResult();for(var prop in result){$.ua[prop]=result[prop]}}}})(typeof window==="object"?window:this);
\ No newline at end of file
diff --git a/dist/ua-parser.min.mjs b/dist/ua-parser.min.mjs
new file mode 100644
index 000000000..c8afed4a9
--- /dev/null
+++ b/dist/ua-parser.min.mjs
@@ -0,0 +1,4 @@
+/* UAParser.js v2.0.4
+ Copyright Š 2012-2025 Faisal Salman
+ AGPLv3 License */
+var LIBVERSION="2.0.4",UA_MAX_LENGTH=500,USER_AGENT="user-agent",EMPTY="",UNKNOWN="?",FUNC_TYPE="function",UNDEF_TYPE="undefined",OBJ_TYPE="object",STR_TYPE="string",UA_BROWSER="browser",UA_CPU="cpu",UA_DEVICE="device",UA_ENGINE="engine",UA_OS="os",UA_RESULT="result",NAME="name",TYPE="type",VENDOR="vendor",VERSION="version",ARCHITECTURE="architecture",MAJOR="major",MODEL="model",CONSOLE="console",MOBILE="mobile",TABLET="tablet",SMARTTV="smarttv",WEARABLE="wearable",XR="xr",EMBEDDED="embedded",INAPP="inapp",BRANDS="brands",FORMFACTORS="formFactors",FULLVERLIST="fullVersionList",PLATFORM="platform",PLATFORMVER="platformVersion",BITNESS="bitness",CH_HEADER="sec-ch-ua",CH_HEADER_FULL_VER_LIST=CH_HEADER+"-full-version-list",CH_HEADER_ARCH=CH_HEADER+"-arch",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],AMAZON="Amazon",APPLE="Apple",ASUS="ASUS",BLACKBERRY="BlackBerry",GOOGLE="Google",HUAWEI="Huawei",LENOVO="Lenovo",HONOR="Honor",LG="LG",MICROSOFT="Microsoft",MOTOROLA="Motorola",NVIDIA="Nvidia",ONEPLUS="OnePlus",OPPO="OPPO",SAMSUNG="Samsung",SHARP="Sharp",SONY="Sony",XIAOMI="Xiaomi",ZEBRA="Zebra",CHROME="Chrome",CHROMIUM="Chromium",CHROMECAST="Chromecast",EDGE="Edge",FIREFOX="Firefox",OPERA="Opera",FACEBOOK="Facebook",SOGOU="Sogou",PREFIX_MOBILE="Mobile ",SUFFIX_BROWSER=" Browser",WINDOWS="Windows";var isWindow=typeof window!==UNDEF_TYPE,NAVIGATOR=isWindow&&window.navigator?window.navigator:undefined,NAVIGATOR_UADATA=NAVIGATOR&&NAVIGATOR.userAgentData?NAVIGATOR.userAgentData:undefined;var extend=function(defaultRgx,extensions){var mergedRgx={};var extraRgx=extensions;if(!isExtensions(extensions)){extraRgx={};for(var i in extensions){for(var j in extensions[i]){extraRgx[j]=extensions[i][j].concat(extraRgx[j]?extraRgx[j]:[])}}}for(var k in defaultRgx){mergedRgx[k]=extraRgx[k]&&extraRgx[k].length%2===0?extraRgx[k].concat(defaultRgx[k]):defaultRgx[k]}return mergedRgx},enumerize=function(arr){var enums={};for(var i=0;i0){for(var i in str1){if(lowerize(str2)==lowerize(str1[i]))return true}return false}return isString(str1)?lowerize(str2)==lowerize(str1):false},isExtensions=function(obj,deep){for(var prop in obj){return/^(browser|cpu|device|engine|os)$/.test(prop)||(deep?isExtensions(obj[prop]):false)}},isString=function(val){return typeof val===STR_TYPE},itemListToArray=function(header){if(!header)return undefined;var arr=[];var tokens=strip(/\\?\"/g,header).split(",");for(var i=0;i-1){var token=trim(tokens[i]).split(";v=");arr[i]={brand:token[0],version:token[1]}}else{arr[i]=trim(tokens[i])}}return arr},lowerize=function(str){return isString(str)?str.toLowerCase():str},majorize=function(version){return isString(version)?strip(/[^\d\.]/g,version).split(".")[0]:undefined},setProps=function(arr){for(var i in arr){if(!arr.hasOwnProperty(i))continue;var propName=arr[i];if(typeof propName==OBJ_TYPE&&propName.length==2){this[propName[0]]=propName[1]}else{this[propName]=undefined}}return this},strip=function(pattern,str){return isString(str)?str.replace(pattern,EMPTY):str},stripQuotes=function(str){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)}};var rgxMapper=function(ua,arrays){if(!ua||!arrays)return;var i=0,j,k,p,q,matches,match;while(i0){if(q.length===2){if(typeof q[1]==FUNC_TYPE){this[q[0]]=q[1].call(this,match)}else{this[q[0]]=q[1]}}else if(q.length>=3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){if(q.length>3){this[q[0]]=match?q[1].apply(this,q.slice(2)):undefined}else{this[q[0]]=match?q[1].call(this,match,q[2]):undefined}}else{if(q.length==3){this[q[0]]=match?match.replace(q[1],q[2]):undefined}else if(q.length==4){this[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}else if(q.length>4){this[q[0]]=match?q[3].apply(this,[match.replace(q[1],q[2])].concat(q.slice(4))):undefined}}}}else{this[q]=match?match:undefined}}}}i+=2}},strMapper=function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;j2){this.set(MODEL,"iPad").set(TYPE,TABLET)}break;case UA_OS: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=13?"11":"10";this.set(NAME,osName).set(VERSION,osVersion)}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],["uaCH",uaCH],["rgxMap",rgxMap],["data",createIData(this,itemType)]]);return this}function UAParser(ua,extensions,headers){if(typeof ua===OBJ_TYPE){if(isExtensions(ua,true)){if(typeof extensions===OBJ_TYPE){headers=extensions}extensions=ua}else{headers=ua;extensions=undefined}ua=undefined}else if(typeof ua===STR_TYPE&&!isExtensions(extensions,true)){headers=extensions;extensions=undefined}if(headers&&typeof headers.append===FUNC_TYPE){var kv={};headers.forEach(function(v,k){kv[k]=v});headers=kv}if(!(this instanceof UAParser)){return new UAParser(ua,extensions,headers).getResult()}var userAgent=typeof ua===STR_TYPE?ua:headers&&headers[USER_AGENT]?headers[USER_AGENT]:NAVIGATOR&&NAVIGATOR.userAgent?NAVIGATOR.userAgent:EMPTY,httpUACH=new UACHData(headers,true),regexMap=extensions?extend(defaultRegexes,extensions):defaultRegexes,createItemFunc=function(itemType){if(itemType==UA_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()).get()}}else{return function(){return new UAItem(itemType,userAgent,regexMap[itemType],httpUACH).parseUA().get()}}};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)],["getUA",function(){return userAgent}],["setUA",function(ua){if(isString(ua))userAgent=ua.length>UA_MAX_LENGTH?trim(ua,UA_MAX_LENGTH):ua;return this}]]).setUA(userAgent);return this}UAParser.VERSION=LIBVERSION;UAParser.BROWSER=enumerize([NAME,VERSION,MAJOR,TYPE]);UAParser.CPU=enumerize([ARCHITECTURE]);UAParser.DEVICE=enumerize([MODEL,VENDOR,TYPE,CONSOLE,MOBILE,SMARTTV,TABLET,WEARABLE,EMBEDDED]);UAParser.ENGINE=UAParser.OS=enumerize([NAME,VERSION]);export{UAParser};
\ No newline at end of file
diff --git a/dist/ua-parser.pack.js b/dist/ua-parser.pack.js
index 1d94f6052..fec42a72e 100644
--- a/dist/ua-parser.pack.js
+++ b/dist/ua-parser.pack.js
@@ -1,4 +1,4 @@
-/* UAParser.js v0.7.33
- Copyright Š 2012-2021 Faisal Salman
- MIT License */
-!function(r,l){"use strict";function i(i){for(var e={},o=0;o
+ AGPLv3 License */
+((i,c)=>{function I(i){for(var e={},t=0;t{var t,o={},r=e;if(!Ti(e))for(var a in r={},e)for(var s in e[a])r[s]=e[a][s].concat(r[s]||[]);for(t in i)o[t]=r[t]&&r[t].length%2==0?r[t].concat(i[t]):i[t];return o})(Ui,e):Ui,M.call(this,[["getBrowser",(n=function(i){return i==g?function(){return new Gi(i,r,s,a).set("ua",r).set(u,this.getBrowser()).set(h,this.getCPU()).set(p,this.getDevice()).set(m,this.getEngine()).set(f,this.getOS()).get()}:function(){return new Gi(i,r,s[i],a).parseUA().get()}})(u)],["getCPU",n(h)],["getDevice",n(p)],["getEngine",n(m)],["getOS",n(f)],["getResult",n(g)],["getUA",function(){return r}],["setUA",function(i){return H(i)&&(r=i.length>P?Mi(i,P):i),this}]]).setUA(r),this):new V(i,e,t).getResult()}V.VERSION="2.0.4",V.BROWSER=I([v,y,G,k]),V.CPU=I([C]),V.DEVICE=I([S,x,k,W,_,e,r,t,F]),V.ENGINE=V.OS=I([v,y]),typeof exports!==n?(exports=typeof module!==n&&module.exports?module.exports=V:exports).UAParser=V:typeof define===R&&define.amd?define(function(){return V}):qi&&(i.UAParser=V);var Wi,Ni=qi&&(i.jQuery||i.Zepto);Ni&&!Ni.ua&&(Wi=new V,Ni.ua=Wi.getResult(),Ni.ua.get=function(){return Wi.getUA()},Ni.ua.set=function(i){Wi.setUA(i);var e,t=Wi.getResult();for(e in t)Ni.ua[e]=t[e]})})("object"==typeof window?window:this);
\ No newline at end of file
diff --git a/dist/ua-parser.pack.mjs b/dist/ua-parser.pack.mjs
new file mode 100644
index 000000000..8ab1e0ee1
--- /dev/null
+++ b/dist/ua-parser.pack.mjs
@@ -0,0 +1,4 @@
+/* UAParser.js v2.0.4
+ Copyright Š 2012-2025 Faisal Salman
+ AGPLv3 License */
+function I(i){for(var e={},o=0;o{var o,t={},r=e;if(!zi(e))for(var a in r={},e)for(var s in e[a])r[s]=e[a][s].concat(r[s]||[]);for(o in i)t[o]=r[o]&&r[o].length%2==0?r[o].concat(i[o]):i[o];return t})(Ii,e):Ii,A.call(this,[["getBrowser",(n=function(i){return i==f?function(){return new Li(i,r,s,a).set("ua",r).set(c,this.getBrowser()).set(h,this.getCPU()).set(u,this.getDevice()).set(p,this.getEngine()).set(m,this.getOS()).get()}:function(){return new Li(i,r,s[i],a).parseUA().get()}})(c)],["getCPU",n(h)],["getDevice",n(u)],["getEngine",n(p)],["getOS",n(m)],["getResult",n(f)],["getUA",function(){return r}],["setUA",function(i){return H(i)&&(r=i.length>P?Mi(i,P):i),this}]]).setUA(r),this):new V(i,e,o).getResult()}V.VERSION="2.0.4",V.BROWSER=I([g,x,C,v]),V.CPU=I([y]),V.DEVICE=I([S,k,v,G,_,i,r,e,N]),V.ENGINE=V.OS=I([g,x]);export{V as UAParser};
\ No newline at end of file
diff --git a/license.md b/license.md
deleted file mode 100644
index 01d840a46..000000000
--- a/license.md
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2012-2021 Faisal Salman <>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..1265e3dd9
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2913 @@
+{
+ "name": "ua-parser-js",
+ "version": "2.0.4",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ua-parser-js",
+ "version": "2.0.4",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "license": "AGPL-3.0-or-later",
+ "dependencies": {
+ "detect-europe-js": "^0.1.2",
+ "is-standalone-pwa": "^0.1.1",
+ "ua-is-frozen": "^0.1.2",
+ "undici": "^7.12.0"
+ },
+ "bin": {
+ "ua-parser-js": "script/cli.js"
+ },
+ "devDependencies": {
+ "@babel/parser": "7.15.8",
+ "@babel/traverse": "7.23.2",
+ "@playwright/test": "^1.49.0",
+ "jshint": "~2.13.6",
+ "mocha": "~8.2.0",
+ "requirejs": "2.3.2",
+ "safe-regex": "^2.1.1",
+ "tsd": "^0.29.0",
+ "uglify-js": "~3.19.3"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
+ "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.26.2",
+ "@babel/types": "^7.26.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
+ "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
+ "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
+ "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
+ "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.15.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz",
+ "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==",
+ "dev": true,
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
+ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template/node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.23.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
+ "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.22.13",
+ "@babel/generator": "^7.23.0",
+ "@babel/helper-environment-visitor": "^7.22.20",
+ "@babel/helper-function-name": "^7.23.0",
+ "@babel/helper-hoist-variables": "^7.22.5",
+ "@babel/helper-split-export-declaration": "^7.22.6",
+ "@babel/parser": "^7.23.0",
+ "@babel/types": "^7.23.0",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/parser": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
+ "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.26.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
+ "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz",
+ "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.49.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true
+ },
+ "node_modules/@tsd/typescript": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-VtjHPAKJqLJoHHKBDNofzvQB2+ZVxjXU/Gw6INAS9aINLQYVsxfzrQ2s84huCeYWZRTtrr7R0J7XgpZHjNwBCw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/@types/eslint": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz",
+ "integrity": "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
+ "dev": true
+ },
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
+ "dev": true
+ },
+ "node_modules/@ungap/promise-all-settled": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
+ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
+ "dev": true
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-keys": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+ "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "map-obj": "^4.0.0",
+ "quick-lru": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
+ "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.5.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.1.2"
+ }
+ },
+ "node_modules/cli": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
+ "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==",
+ "dev": true,
+ "dependencies": {
+ "exit": "0.1.2",
+ "glob": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=0.2.5"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==",
+ "dev": true,
+ "dependencies": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true
+ },
+ "node_modules/date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decamelize-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+ "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+ "dev": true,
+ "dependencies": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decamelize-keys/node_modules/map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-europe-js": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz",
+ "integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ }
+ ]
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+ "dev": true,
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ }
+ },
+ "node_modules/dom-serializer/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ]
+ },
+ "node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
+ "dev": true
+ },
+ "node_modules/domhandler": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+ "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==",
+ "dev": true,
+ "dependencies": {
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==",
+ "dev": true,
+ "dependencies": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/entities": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+ "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-formatter-pretty": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz",
+ "integrity": "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/eslint": "^7.2.13",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.1.0",
+ "eslint-rule-docs": "^1.1.5",
+ "log-symbols": "^4.0.0",
+ "plur": "^4.0.0",
+ "string-width": "^4.2.0",
+ "supports-hyperlinks": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-rule-docs": {
+ "version": "1.1.235",
+ "resolved": "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz",
+ "integrity": "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==",
+ "dev": true
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.x"
+ }
+ },
+ "node_modules/hard-rejection": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/hosted-git-info/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/hosted-git-info/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/htmlparser2": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+ "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==",
+ "dev": true,
+ "dependencies": {
+ "domelementtype": "1",
+ "domhandler": "2.3",
+ "domutils": "1.5",
+ "entities": "1.0",
+ "readable-stream": "1.1"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/irregular-plurals": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz",
+ "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-standalone-pwa": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz",
+ "integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ }
+ ]
+ },
+ "node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+ "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+ "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jshint": {
+ "version": "2.13.6",
+ "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz",
+ "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==",
+ "dev": true,
+ "dependencies": {
+ "cli": "~1.0.0",
+ "console-browserify": "1.1.x",
+ "exit": "0.1.x",
+ "htmlparser2": "3.8.x",
+ "lodash": "~4.17.21",
+ "minimatch": "~3.0.2",
+ "strip-json-comments": "1.0.x"
+ },
+ "bin": {
+ "jshint": "bin/jshint"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/log-symbols": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
+ "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/map-obj": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
+ "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/minimist": "^1.2.0",
+ "camelcase-keys": "^6.2.2",
+ "decamelize": "^1.2.0",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
+ "read-pkg-up": "^7.0.1",
+ "redent": "^3.0.0",
+ "trim-newlines": "^3.0.0",
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/type-fest": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
+ "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist-options": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz",
+ "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==",
+ "dev": true,
+ "dependencies": {
+ "@ungap/promise-all-settled": "1.1.2",
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.4.3",
+ "debug": "4.2.0",
+ "diff": "4.0.2",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.1.6",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "3.14.0",
+ "log-symbols": "4.0.0",
+ "minimatch": "3.0.4",
+ "ms": "2.1.2",
+ "nanoid": "3.1.12",
+ "serialize-javascript": "5.0.1",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "7.2.0",
+ "which": "2.0.2",
+ "wide-align": "1.1.3",
+ "workerpool": "6.0.2",
+ "yargs": "13.3.2",
+ "yargs-parser": "13.1.2",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha"
+ },
+ "engines": {
+ "node": ">= 10.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/mocha/node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mocha/node_modules/cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ }
+ },
+ "node_modules/mocha/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/mocha/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/debug": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
+ "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
+ "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mocha/node_modules/emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mocha/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mocha/node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mocha/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mocha/node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mocha/node_modules/string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mocha/node_modules/wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/mocha/node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/yargs": {
+ "version": "13.3.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.2"
+ }
+ },
+ "node_modules/mocha/node_modules/yargs/node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
+ "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/normalize-package-data": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+ "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^4.0.1",
+ "is-core-module": "^2.5.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-package-data/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz",
+ "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.49.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz",
+ "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/plur": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz",
+ "integrity": "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==",
+ "dev": true,
+ "dependencies": {
+ "irregular-plurals": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-lru": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true
+ },
+ "node_modules/read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+ "dev": true,
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg/node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true
+ },
+ "node_modules/read-pkg/node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/read-pkg/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+ "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/regexp-tree": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
+ "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
+ "dev": true,
+ "bin": {
+ "regexp-tree": "bin/regexp-tree"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "node_modules/requirejs": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.2.tgz",
+ "integrity": "sha512-rGSBJSIWfUEkMTztpZlyGIpRIwuMau6gZYxQhxH1jCggR3ddWsI8+/aWh9yUscMB1O0pEgZrJiXy2hJerzMszA==",
+ "dev": true,
+ "bin": {
+ "r_js": "bin/r.js",
+ "r.js": "bin/r.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safe-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
+ "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==",
+ "dev": true,
+ "dependencies": {
+ "regexp-tree": "~0.1.1"
+ }
+ },
+ "node_modules/serialize-javascript": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
+ "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "dev": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.20",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz",
+ "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==",
+ "dev": true
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
+ "dev": true
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+ "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==",
+ "dev": true,
+ "bin": {
+ "strip-json-comments": "cli.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-hyperlinks": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
+ "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/trim-newlines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tsd": {
+ "version": "0.29.0",
+ "resolved": "https://registry.npmjs.org/tsd/-/tsd-0.29.0.tgz",
+ "integrity": "sha512-5B7jbTj+XLMg6rb9sXRBGwzv7h8KJlGOkTHxY63eWpZJiQ5vJbXEjL0u7JkIxwi5EsrRE1kRVUWmy6buK/ii8A==",
+ "dev": true,
+ "dependencies": {
+ "@tsd/typescript": "~5.2.2",
+ "eslint-formatter-pretty": "^4.1.0",
+ "globby": "^11.0.1",
+ "jest-diff": "^29.0.3",
+ "meow": "^9.0.0",
+ "path-exists": "^4.0.0",
+ "read-pkg-up": "^7.0.0"
+ },
+ "bin": {
+ "tsd": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ua-is-frozen": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz",
+ "integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ }
+ ]
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/undici": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz",
+ "integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "dev": true
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "node_modules/wide-align/node_modules/ansi-regex": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wide-align/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wide-align/node_modules/string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "dependencies": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wide-align/node_modules/strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/workerpool": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz",
+ "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==",
+ "dev": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/yargs-parser": {
+ "version": "13.1.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs-unparser/node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.js b/package.js
deleted file mode 100644
index a3bcdb348..000000000
--- a/package.js
+++ /dev/null
@@ -1,12 +0,0 @@
-Package.describe({
- name: 'faisalman:ua-parser-js',
- version: '0.7.33',
- summary: 'Lightweight JavaScript-based user-agent string parser',
- git: 'https://github.com/faisalman/ua-parser-js.git',
- documentation: 'readme.md'
-});
-
-Package.onUse(function (api) {
- api.addFiles("src/ua-parser.js");
- api.export("UAParser");
-});
diff --git a/package.json b/package.json
old mode 100644
new mode 100755
index 096a43fa0..0f21208a1
--- a/package.json
+++ b/package.json
@@ -1,11 +1,12 @@
{
"title": "UAParser.js",
"name": "@amplitude/ua-parser-js",
- "version": "0.7.33",
+ "version": "2.0.4",
"author": "Faisal Salman (http://faisalman.com)",
- "description": "Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data. Supports browser & node.js environment",
+ "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",
@@ -13,20 +14,28 @@
"device",
"cpu",
"jquery-plugin",
- "ecosystem:jquery"
+ "ecosystem:jquery",
+ "ua-parser-js",
+ "browser-detection",
+ "device-detection",
+ "os-detection",
+ "bot-detection"
],
"homepage": "http://github.com/amplitude/ua-parser-js",
"contributors": [
"Aamir Poonawalla ",
"Admas ",
+ "Aiyush ",
"algenon ",
"Alvin Portillo ",
"Amumu ",
+ "Andreas Kogler ",
"Andrea Vaghi ",
"Anton Zhiyanov ",
"Arturo Mejia ",
"Arun Rama Reddy ",
"Austin Pray ",
+ "Beat YT <66485277+Beat-YT@users.noreply.github.com>",
"Bendeguz ",
"Benjamin Bertrand ",
"Benjamin Urban ",
@@ -34,8 +43,13 @@
"Carl C Von Lewin ",
"CESAR RAMOS ",
"Chad Killingsworth ",
+ "chenhui9279 ",
+ "chenyuan-new <53860479+chenyuan-new@users.noreply.github.com>",
"Christopher De Cairos ",
"Cyrille David ",
+ "Dai Jie ",
+ "Danila Rodichkin ",
+ "Dante ",
"Dario Vladovic ",
"David Annez ",
"Davit Barbakadze ",
@@ -58,17 +72,24 @@
"Faisal Salman ",
"FrÊdÊric Camblor ",
"Frederik Ring ",
+ "Garrit Franke ",
"Gerald Host ",
"GermÃĄn M. Bravo ",
"Grigory Dmitrenko ",
"gulpin ",
+ "Hans Ott ",
+ "Harald Reingruber <74898239+haraldreingruber-dedalus@users.noreply.github.com>",
"Hendrik Helwich ",
"Hermann Ebert ",
"hr6r ",
+ "Hyewon Kang ",
+ "Hyunbin <47051820+hyunbinseo@users.noreply.github.com>",
"Igor Topal ",
"Ildar Kamalov ",
+ "Ilya Daraseliya ",
"insanehong ",
"jackpoll ",
+ "Jacky Choo ",
"Jake Mc ",
"JBYoshi <12983479+JBYoshi@users.noreply.github.com>",
"Joey Parrish ",
@@ -78,11 +99,15 @@
"Josh Goldberg ",
"Junki-Ishida ",
"Kendall Buchanan ",
+ "KnifeLemon ",
+ "kNoAPP ",
"Lee Treveil ",
"leonardo ",
"Levente Balogh ",
"Liam Quinn ",
"Lithin ",
+ "liujunlve ",
+ "lj0812 ",
"ll-syber <670159357@qq.com>",
"Loris Guignard ",
"Lukas Drgon ",
@@ -96,7 +121,10 @@
"Max Maurer ",
"Max Nordlund ",
"Michael Hess ",
+ "Mike ",
"MimyyK ",
+ "Mok ",
+ "nabetama ",
"naoh ",
"Nicholas Ionata ",
"Nikhil Motiani ",
@@ -105,7 +133,10 @@
"niris ",
"Nobuo Okada ",
"o.drapeza ",
+ "Oscar Becerra ",
"otakuSiD ",
+ "Pablo OsÊs ",
+ "Paris Morgan ",
"patrick-nurt ",
"Pavel Studeny ",
"Peter Dave Hello ",
@@ -115,19 +146,25 @@
"Queen Vinyl Darkscratch ",
"Raine Makelainen ",
"Raman Savaryn ",
+ "Riley Shaw ",
"Robert Tod ",
"roman.savarin ",
"Ron Korland ",
"Ross Noble ",
"ruicong <466403866@qq.com>",
+ "Runar Heggset ",
+ "Ryohei Shima ",
+ "Ryan Haines ",
"Sandro Sonntag ",
"sgautrea ",
+ "shaharmor ",
"Shane Gautreau ",
"Shane Thacker ",
"Shreedhar ",
"Simon Eisenmann ",
"Simon Lang ",
"Stiekel ",
+ "sunny-mwx <30586210+sunny-mwx@users.noreply.github.com>",
"sUP ",
"Sylvain Gizard ",
"szchenghuang ",
@@ -135,60 +172,92 @@
"Tony Tomarchio ",
"Ulrich Schmidt ",
"Vadim Kurachevsky ",
+ "Varun Sharma ",
+ "XhmikosR ",
"YÄąlmaz ",
"yuanyang ",
"Yun Young-jin ",
- "Zach Bjornson "
+ "Zach Bjornson ",
+ "Ziding Zhang "
],
- "main": "src/ua-parser.js",
+ "type": "commonjs",
+ "types": "src/main/ua-parser.d.ts",
+ "main": "src/main/ua-parser.js",
+ "module": "src/main/ua-parser.mjs",
+ "browser": "dist/ua-parser.pack.js",
+ "exports": {
+ ".": {
+ "require": "./src/main/ua-parser.js",
+ "import": "./src/main/ua-parser.mjs",
+ "types": "./src/main/ua-parser.d.ts"
+ },
+ "./enums": {
+ "require": "./src/enums/ua-parser-enums.js",
+ "import": "./src/enums/ua-parser-enums.mjs",
+ "types": "./src/enums/ua-parser-enums.d.ts"
+ },
+ "./extensions": {
+ "require": "./src/extensions/ua-parser-extensions.js",
+ "import": "./src/extensions/ua-parser-extensions.mjs",
+ "types": "./src/extensions/ua-parser-extensions.d.ts"
+ },
+ "./helpers": {
+ "require": "./src/helpers/ua-parser-helpers.js",
+ "import": "./src/helpers/ua-parser-helpers.mjs",
+ "types": "./src/helpers/ua-parser-helpers.d.ts"
+ }
+ },
"files": [
"dist",
"src"
],
+ "bin": "./script/cli.js",
"scripts": {
- "build": "uglifyjs src/ua-parser.js -o dist/ua-parser.min.js --comments '/^ UA/' && uglifyjs src/ua-parser.js -o dist/ua-parser.pack.js --comments '/^ UA/' --compress --mangle",
- "test": "jshint src/ua-parser.js && mocha -R nyan test/test.js",
- "test-ci": "jshint src/ua-parser.js && mocha -R spec test/test.js",
- "verup": "node ./node_modules/verup",
- "version": "node ./node_modules/verup 0"
+ "build": "./script/build-dist.sh && ./script/build-esm.js",
+ "build+test": "npm run build && npm run test",
+ "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:jshint": "jshint src/main",
+ "test:lockfile-lint": "npx lockfile-lint -p package-lock.json",
+ "test:mocha": "mocha test/unit",
+ "test:playwright": "npx playwright install && playwright test test/e2e --browser all"
},
- "verup": {
- "files": [
- "bower.json",
- "package.js",
- "src/ua-parser.js"
- ],
- "regs": [
- "^((?:\\$|(\\s*\\*\\s*@)|(\\s*(?:var|,)?\\s+))(?:LIBVERSION|version)[\\s\\:='\"]+)([0-9]+(?:\\.[0-9]+){2,2})",
- "^(\\/?\\s?\\*.*v)([0-9]+(?:\\.[0-9]+){2,2})"
- ]
+ "dependencies": {
+ "detect-europe-js": "^0.1.2",
+ "is-standalone-pwa": "^0.1.1",
+ "ua-is-frozen": "^0.1.2",
+ "undici": "^7.12.0"
},
"devDependencies": {
"@babel/parser": "7.15.8",
- "@babel/traverse": "7.15.4",
- "jshint": "~2.12.0",
+ "@babel/traverse": "7.23.2",
+ "@playwright/test": "^1.49.0",
+ "jshint": "~2.13.6",
"mocha": "~8.2.0",
- "requirejs": "^2.3.2",
+ "requirejs": "2.3.2",
"safe-regex": "^2.1.1",
- "uglify-js": "~3.12.0",
- "verup": "^1.3.x"
+ "tsd": "^0.29.0",
+ "uglify-js": "~3.19.3"
},
"repository": {
"type": "git",
"url": "https://github.com/amplitude/ua-parser-js.git"
},
- "license": "MIT",
+ "license": "AGPL-3.0-or-later",
"engines": {
"node": "*"
},
"directories": {
"dist": "dist",
+ "script": "script",
"src": "src",
"test": "test"
},
"bugs": "https://github.com/amplitude/ua-parser-js/issues",
- "demo": "https://faisalman.github.io/ua-parser-js",
- "download": "https://raw.github.com/faisalman/ua-parser-js/master/dist/ua-parser.min.js",
+ "demo": "https://uaparser.dev",
+ "download": "https://raw.github.com/faisalman/ua-parser-js/master/dist/ua-parser.pack.js",
"funding": [
{
"type": "opencollective",
@@ -197,6 +266,10 @@
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
}
]
}
diff --git a/readme.md b/readme.md
deleted file mode 100644
index 71bb1879d..000000000
--- a/readme.md
+++ /dev/null
@@ -1,359 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-# UAParser.js
-
-JavaScript library to detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data with relatively small footprint (~17KB minified, ~6KB gzipped) that can be used either in browser (client-side) or node.js (server-side).
-
-* Author : Faisal Salman <>
-* Demo : https://faisalman.github.io/ua-parser-js
-* Source : https://github.com/faisalman/ua-parser-js
-
-# Documentation
-### UAParser([user-agent][,extensions])
-typeof `user-agent` "string".
-
-typeof `extensions` "array".
-
-In The Browser environment you dont need to pass the user-agent string to the function, you can just call the funtion and it should automatically get the string from the `window.navigator.userAgent`, but that is not the case in nodejs. The user-agent string must be passed in nodejs for the function to work.
-Usually you can find the user agent in:
-`request.headers["user-agent"]`.
-
-
-## Constructor
-When you call `UAParser` with the `new` keyword `UAParser` will return a new instance with an empty result object, you have to call one of the available methods to get the information from the user-agent string.
-Like so:
-* `new UAParser([uastring][,extensions])`
-```js
-let parser = new UAParser("user-agent"); // you need to pass the user-agent for nodejs
-console.log(parser); // {}
-let parserResults = parser.getResult();
-console.log(parserResults);
-/** {
- "ua": "",
- "browser": {},
- "engine": {},
- "os": {},
- "device": {},
- "cpu": {}
-} */
-```
-
-When you call UAParser without the `new` keyword, it will automatically call `getResult()` function and return the parsed results.
-* `UAParser([uastring][,extensions])`
- * returns result object `{ ua: '', browser: {}, cpu: {}, device: {}, engine: {}, os: {} }`
-
-## Methods
-
-#### Methods table
-The methods are self explanatory, here's a small overview on all the available methods:
-* `getResult()` - returns all function object calls, user-agent string, browser info, cpu, device, engine, os:
-`{ ua: '', browser: {}, cpu: {}, device: {}, engine: {}, os: {} }`.
-
- * `getBrowser()` - returns the browser name and version.
- * `getDevice()` - returns the device model, type, vendor.
- * `getEngine()` - returns the current browser engine name and version.
- * `getOS()` - returns the running operating system name and version.
- * `getCPU()` - returns CPU architectural design name.
- * `getUA()` - returns the user-agent string.
- * `setUA(user-agent)` - set a custom user-agent to be parsed.
-
-
----
-
-* `getResult()`
- * returns `{ ua: '', browser: {}, cpu: {}, device: {}, engine: {}, os: {} }`
-
-* `getBrowser()`
- * returns `{ name: '', version: '' }`
-
-```sh
-# Possible 'browser.name':
-2345Explorer, 360 Browser, Amaya, Android Browser, Arora, Avant, Avast, AVG,
-BIDUBrowser, Baidu, Basilisk, Blazer, Bolt, Brave, Bowser, Camino, Chimera,
-Chrome Headless, Chrome WebView, Chrome, Chromium, Comodo Dragon, Dillo,
-Dolphin, Doris, DuckDuckGo, Edge, Electron, Epiphany, Facebook, Falkon, Fennec,
-Firebird, Firefox [Focus/Reality], Flock, Flow, GSA, GoBrowser, Huawei Browser,
-ICE Browser, IE, IEMobile, IceApe, IceCat, IceDragon, Iceweasel, Instagram,
-Iridium, Iron, Jasmine, K-Meleon, Kindle, Klar, Konqueror, LBBROWSER, Line,
-LinkedIn, Links, Lunascape, Lynx, MIUI Browser, Maemo Browser, Maemo, Maxthon,
-MetaSr Midori, Minimo, Mobile Safari, Mosaic, Mozilla, NetFront, NetSurf, Netfront,
-Netscape, NokiaBrowser, Obigo, Oculus Browser, OmniWeb, Opera Coast,
-Opera [Mini/Mobi/Tablet], PaleMoon, PhantomJS, Phoenix, Polaris, Puffin, QQ,
-QQBrowser, QQBrowserLite, Quark, QupZilla, RockMelt, Safari, Sailfish Browser,
-Samsung Browser, SeaMonkey, Silk, Skyfire, Sleipnir, Slim, SlimBrowser, Swiftfox,
-Tesla, Tizen Browser, UCBrowser, UP.Browser, Vivaldi, Waterfox, WeChat, Weibo,
-Yandex, baidu, iCab, w3m, Whale Browser...
-
-# 'browser.version' determined dynamically
-```
-
-* `getDevice()`
- * returns `{ model: '', type: '', vendor: '' }`
-
-```sh
-# Possible 'device.type':
-console, mobile, tablet, smarttv, wearable, embedded
-
-##########
-# NOTE: 'desktop' is not a possible device type.
-# UAParser only reports info directly available from the UA string, which is not the case for 'desktop' device type.
-# If you wish to detect desktop devices, you must handle the needed logic yourself.
-# You can read more about it in this issue: https://github.com/faisalman/ua-parser-js/issues/182
-##########
-
-# Possible 'device.vendor':
-Acer, Alcatel, Amazon, Apple, Archos, ASUS, AT&T, BenQ, BlackBerry, Dell,
-Essential, Fairphone, GeeksPhone, Google, HP, HTC, Huawei, Jolla, Lenovo, LG,
-Meizu, Microsoft, Motorola, Nexian, Nintendo, Nokia, Nvidia, OnePlus, OPPO, Ouya,
-Palm, Panasonic, Pebble, Polytron, Realme, RIM, Roku, Samsung, Sharp, Siemens,
-Sony[Ericsson], Sprint, Tesla, Vivo, Vodafone, Xbox, Xiaomi, Zebra, ZTE, ...
-
-# 'device.model' determined dynamically
-```
-
-* `getEngine()`
- * returns `{ name: '', version: '' }`
-
-```sh
-# Possible 'engine.name'
-Amaya, Blink, EdgeHTML, Flow, Gecko, Goanna, iCab, KHTML, Links, Lynx, NetFront,
-NetSurf, Presto, Tasman, Trident, w3m, WebKit
-
-# 'engine.version' determined dynamically
-```
-
-* `getOS()`
- * returns `{ name: '', version: '' }`
-
-```sh
-# Possible 'os.name'
-AIX, Amiga OS, Android[-x86], Arch, Bada, BeOS, BlackBerry, CentOS, Chromium OS,
-Contiki, Fedora, Firefox OS, FreeBSD, Debian, Deepin, DragonFly, elementary OS,
-Fuchsia, Gentoo, GhostBSD, GNU, Haiku, HarmonyOS, HP-UX, Hurd, iOS, Joli, KaiOS,
-Linpus, Linspire,Linux, Mac OS, Maemo, Mageia, Mandriva, Manjaro, MeeGo, Minix,
-Mint, Morph OS, NetBSD, Nintendo, OpenBSD, OpenVMS, OS/2, Palm, PC-BSD, PCLinuxOS,
-Plan9, PlayStation, QNX, Raspbian, RedHat, RIM Tablet OS, RISC OS, Sabayon,
-Sailfish, Series40, Slackware, Solaris, SUSE, Symbian, Tizen, Ubuntu, Unix,
-VectorLinux, WebOS, Windows [Phone/Mobile], Zenwalk, ...
-
-# 'os.version' determined dynamically
-```
-
-* `getCPU()`
- * returns `{ architecture: '' }`
-
-```sh
-# Possible 'cpu.architecture'
-68k, amd64, arm[64/hf], avr, ia[32/64], irix[64], mips[64], pa-risc, ppc, sparc[64]
-```
-
-* `getUA()`
- * returns UA string of current instance
-
-* `setUA(uastring)`
- * set UA string to be parsed
- * returns current instance
-
-# Usage
-
-## Using HTML
-
-```html
-
-
-
-
-
-
-
-
-
-```
-
-## Using node.js
-
-Note: Device information is not available in the NodeJS environment.
-
-```sh
-$ npm install ua-parser-js
-```
-
-```js
-var http = require('http');
-var parser = require('ua-parser-js');
-
-http.createServer(function (req, res) {
- // get user-agent header
- var ua = parser(req.headers['user-agent']);
- // write the result as response
- res.end(JSON.stringify(ua, null, ' '));
-})
-.listen(1337, '127.0.0.1');
-
-console.log('Server running at http://127.0.0.1:1337/');
-```
-
-## Using TypeScript
-
-```sh
-$ npm install --save @types/ua-parser-js
-# Download TS type definition from DefinitelyTyped repository:
-# https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/ua-parser-js
-```
-
-## Using jQuery/Zepto ($.ua)
-
-Although written in vanilla js, this library will automatically detect if jQuery/Zepto is present and create `$.ua` object (with values based on its User-Agent) along with `window.UAParser` constructor. To get/set user-agent you can use: `$.ua.get()` / `$.ua.set(uastring)`.
-
-```js
-// Say we are in a browser with default user-agent: 'Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Sprint APA7373KT Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0':
-
-// Get the details
-console.log($.ua.device); // {vendor: "HTC", model: "Evo Shift 4G", type: "mobile"}
-console.log($.ua.os); // {name: "Android", version: "2.3.4"}
-console.log($.ua.os.name); // "Android"
-console.log($.ua.get()); // "Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Sprint APA7373KT Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0"
-
-// Now lets try to reset to another custom user-agent
-$.ua.set('Mozilla/5.0 (Linux; U; Android 3.0.1; en-us; Xoom Build/HWI69) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13');
-
-// Test again
-console.log($.ua.browser.name); // "Safari"
-console.log($.ua.engine.name); // "Webkit"
-console.log($.ua.device); // {vendor: "Motorola", model: "Xoom", type: "tablet"}
-console.log(parseInt($.ua.browser.version.split('.')[0], 10)); // 4
-
-// Add class to tag
-//
-$('body').addClass('ua-browser-' + $.ua.browser.name + ' ua-devicetype-' + $.ua.device.type);
-```
-
-## Using Extension
-
-* `UAParser([uastring,] extensions)`
-
-```js
-// Example:
-var myOwnListOfBrowsers = [
- [/(mybrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION]
-];
-var myParser = new UAParser({ browser: myOwnListOfBrowsers });
-var myUA = 'Mozilla/5.0 MyBrowser/1.3';
-console.log(myParser.setUA(myUA).getBrowser()); // {name: "MyBrowser", version: "1.3"}
-```
-
-# Development
-
-## Sponsors
-
-
-
-
-
-## Contributors
-
-
-
-
-
-Made with [contributors-img](https://contrib.rocks).
-
-## How To Contribute
-
-* Fork and clone this repository
-* Make some changes as required
-* Write unit test to showcase its functionality
-* Run the test suites to make sure it's not breaking anything `$ npm test`
-* Submit a pull request under `develop` branch
-
-# License
-
-MIT License
-
-Copyright (c) 2012-2021 Faisal Salman <>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/script/build-dist.sh b/script/build-dist.sh
new file mode 100755
index 000000000..0648a12df
--- /dev/null
+++ b/script/build-dist.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+SRC_PATH="src/main/ua-parser.js"
+MIN_PATH="dist/ua-parser.min.js"
+PACK_PATH="dist/ua-parser.pack.js"
+
+SRC_PATH_MJS="src/main/ua-parser.mjs"
+MIN_PATH_MJS="dist/ua-parser.min.mjs"
+PACK_PATH_MJS="dist/ua-parser.pack.mjs"
+
+# minified
+echo "Generate ${MIN_PATH}"
+uglifyjs $SRC_PATH -o $MIN_PATH --comments "/^ UA/"
+
+echo "Generate ${MIN_PATH_MJS}"
+uglifyjs $SRC_PATH_MJS -o $MIN_PATH_MJS --comments "/^ UA/" --module
+
+# packed
+echo "Generate ${PACK_PATH}"
+uglifyjs $SRC_PATH -o $PACK_PATH --comments "/^ UA/" --compress --mangle
+
+echo "Generate ${PACK_PATH_MJS}"
+uglifyjs $SRC_PATH_MJS -o $PACK_PATH_MJS --comments "/^ UA/" --compress --mangle --module
\ No newline at end of file
diff --git a/script/build-esm.js b/script/build-esm.js
new file mode 100755
index 000000000..98f7ffb8b
--- /dev/null
+++ b/script/build-esm.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+/* jshint esversion: 6 */
+const fs = require('fs');
+
+const generateMJS = (module) => {
+ let { src, dest, title, replacements } = module;
+ let text = fs.readFileSync(src, 'utf-8');
+
+ replacements.push(
+ [/const (.+?)\s*=\s*require\(\'\.(.+)\'\)/ig, 'import $1 from \'\.$2.mjs\''],
+ [/const (.+?)\s*=\s*require\(\'(.+)\'\)/ig, 'import $1 from \'$2\''],
+ [/module\.exports =/ig, 'export']
+ );
+ replacements.forEach(rep => {
+ text = text.replace(rep[0], rep[1]);
+ });
+
+ console.log(`Generate ${dest}`);
+
+ fs.writeFileSync(dest,
+`// Generated ESM version of ${title}
+// DO NOT EDIT THIS FILE!
+// Source: /${src}
+
+${text}`, 'utf-8');
+
+};
+
+const modules = [
+ {
+ src : 'src/main/ua-parser.js',
+ dest : 'src/main/ua-parser.mjs',
+ title : 'ua-parser-js',
+ replacements : [
+ [/\(func[\s\S]+strict\';/ig, ''],
+ [/esversion\: 3/ig, 'esversion: 6'],
+ [/\/[\/\s]+export[\s\S]+/ig,'export {UAParser};']
+ ]
+ },{
+ src : 'src/enums/ua-parser-enums.js',
+ dest :'src/enums/ua-parser-enums.mjs',
+ title : 'ua-parser-js/enums',
+ replacements : []
+ },
+ {
+ src : 'src/extensions/ua-parser-extensions.js',
+ dest : 'src/extensions/ua-parser-extensions.mjs',
+ title : 'ua-parser-js/extensions',
+ replacements : []
+ },
+ {
+ src : 'src/helpers/ua-parser-helpers.js',
+ dest : 'src/helpers/ua-parser-helpers.mjs',
+ title : 'ua-parser-js/helpers',
+ replacements : []
+ }
+];
+
+modules.forEach(module => generateMJS(module));
\ No newline at end of file
diff --git a/script/cli.js b/script/cli.js
new file mode 100755
index 000000000..785a10426
--- /dev/null
+++ b/script/cli.js
@@ -0,0 +1,4 @@
+#!/usr/bin/env node
+
+const UAParser = require('ua-parser-js');
+console.log(JSON.stringify(process.argv.slice(2).map(ua => UAParser(ua)), null, 4));
\ No newline at end of file
diff --git a/script/test-all.sh b/script/test-all.sh
new file mode 100755
index 000000000..f00fea2ed
--- /dev/null
+++ b/script/test-all.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+echo '
+- run build
+'
+npm run build || exit 1
+
+echo '
+- lint js code
+'
+npm run test:jshint || exit 1
+#npm run test:eslint || exit 1
+
+echo '
+- test using mocha
+'
+npm run test:mocha || exit 1
+
+echo '
+- test using playwright
+'
+npm run test:playwright || exit 1
+
+echo '
+- lint lockfile
+'
+npm run test:lockfile-lint || exit 1
+
+echo '
+- lint d.ts files
+'
+npm run test:dts-lint || exit 1
\ No newline at end of file
diff --git a/src/enums/ua-parser-enums.d.ts b/src/enums/ua-parser-enums.d.ts
new file mode 100644
index 000000000..aa40b50f6
--- /dev/null
+++ b/src/enums/ua-parser-enums.d.ts
@@ -0,0 +1,387 @@
+// Type definitions for Enums submodule of UAParser.js v2.0.4
+// Project: https://github.com/faisalman/ua-parser-js
+// Definitions by: Faisal Salman
+
+export const Browser: Readonly<{
+ '115': "115";
+ '2345': "2345";
+ '360': "360";
+ ALIPAY: "Alipay";
+ AMAYA: "Amaya";
+ ANDROID: "Android Browser";
+ ARORA: "Arora";
+ AVANT: "Avant";
+ AVAST: "Avast Secure Browser";
+ AVG: "AVG Secure Browser";
+ BAIDU: "Baidu Browser";
+ BASILISK: "Basilisk";
+ BLAZER: "Blazer";
+ BOLT: "Bolt";
+ BOWSER: "Bowser";
+ BRAVE: "Brave";
+ CAMINO: "Camino";
+ CHIMERA: "Chimera";
+ CHROME: "Chrome";
+ CHROME_HEADLESS: "Chrome Headless";
+ CHROME_MOBILE: "Mobile Chrome";
+ CHROME_WEBVIEW: "Chrome WebView";
+ CHROMIUM: "Chromium";
+ COBALT: "Cobalt";
+ COC_COC: "Coc Coc";
+ CONKEROR: "Conkeror";
+ DAUM: "Daum";
+ DILLO: "Dillo";
+ DOLPHIN: "Dolphin";
+ DOOBLE: 'Dooble',
+ DORIS: "Doris";
+ DRAGON: "Dragon";
+ DUCKDUCKGO: "DuckDuckGo";
+ ECOSIA: "Ecosia";
+ EDGE: "Edge";
+ EDGE_WEBVIEW: "Edge WebView";
+ EDGE_WEBVIEW2: "Edge WebView2";
+ EPIPHANY: "Epiphany";
+ FACEBOOK: "Facebook";
+ FALKON: "Falkon";
+ FIREBIRD: "Firebird";
+ FIREFOX: "Firefox";
+ FIREFOX_FOCUS: "Firefox Focus";
+ FIREFOX_MOBILE: "Mobile Firefox";
+ FIREFOX_REALITY: "Firefox Reality";
+ FENNEC: "Fennec";
+ FLOCK: "Flock";
+ FLOW: "Flow";
+ GO: "GoBrowser";
+ GOOGLE_SEARCH: "GSA";
+ HELIO: "Helio";
+ HEYTAP: "HeyTap";
+ HONOR: "Honor";
+ HUAWEI: "Huawei Browser";
+ ICAB: "iCab";
+ ICE: "ICE Browser";
+ ICEAPE: "IceApe";
+ ICECAT: "IceCat";
+ ICEDRAGON: "IceDragon";
+ ICEWEASEL: "IceWeasel";
+ IE: "IE";
+ INSTAGRAM: "Instagram";
+ IRIDIUM: "Iridium";
+ IRON: "Iron";
+ JASMINE: "Jasmine";
+ KONQUEROR: "Konqueror";
+ KAKAO: "KakaoTalk";
+ KHTML: "KHTML";
+ K_MELEON: "K-Meleon";
+ KLAR: "Klar";
+ KLARNA: "Klarna";
+ KINDLE: "Kindle";
+ LENOVO: "Smart Lenovo Browser";
+ LADYBIRD: "Ladybird";
+ LG: "LG Browser";
+ LIBREWOLF: "LibreWolf";
+ LIEBAO: "LBBROWSER";
+ LINE: "Line";
+ LINKEDIN: "LinkedIn";
+ LINKS: "Links";
+ LUNASCAPE: "Lunascape";
+ LYNX: "Lynx";
+ MAEMO: "Maemo Browser";
+ MAXTHON: "Maxthon";
+ MIDORI: "Midori";
+ MINIMO: "Minimo";
+ MIUI: "MIUI Browser";
+ MOZILLA: "Mozilla";
+ MOSAIC: "Mosaic";
+ NAVER: "Naver";
+ NETFRONT: "NetFront";
+ NETSCAPE: "Netscape";
+ NETSURF: "Netsurf";
+ NOKIA: "Nokia Browser";
+ OBIGO: "Obigo";
+ OCULUS: "Oculus Browser";
+ OMNIWEB: "OmniWeb";
+ OPERA: "Opera";
+ OPERA_COAST: "Opera Coast";
+ OPERA_GX: "Opera GX",
+ OPERA_MINI: "Opera Mini";
+ OPERA_MOBI: "Opera Mobi";
+ OPERA_TABLET: "Opera Tablet";
+ OPERA_TOUCH: "Opera Touch";
+ OTTER: "Otter";
+ OVI: "OviBrowser";
+ PALEMOON: "PaleMoon";
+ PHANTOMJS: "PhantomJS";
+ PHOENIX: "Phoenix";
+ PICOBROWSER: "Pico Browser";
+ POLARIS: "Polaris";
+ PUFFIN: "Puffin";
+ QQ: "QQBrowser";
+ QQ_LITE: "QQBrowserLite";
+ QUARK: "Quark";
+ QUPZILLA: "QupZilla";
+ QUTEBROWSER: "qutebrowser";
+ REKONQ: "rekonq";
+ ROCKMELT: "Rockmelt";
+ SAFARI: "Safari";
+ SAFARI_MOBILE: "Mobile Safari";
+ SAILFISH: "Sailfish Browser";
+ SAMSUNG: "Samsung Internet";
+ SEAMONKEY: "SeaMonkey";
+ SILK: "Silk";
+ SKYFIRE: "Skyfire";
+ SLEIPNIR: "Sleipnir";
+ SLIMBOAT: "SlimBoat";
+ SLIMBROWSER: "SlimBrowser";
+ SLIMJET: "Slimjet";
+ SNAPCHAT: "Snapchat";
+ SOGOU_EXPLORER: "Sogou Explorer";
+ SOGOU_MOBILE: "Sogou Mobile";
+ SURF: "Surf";
+ SWIFTFOX: "Swiftfox";
+ TESLA: "Tesla";
+ TIKTOK: "TikTok";
+ TIZEN: "Tizen Browser";
+ TWITTER: "Twitter";
+ UC: "UCBrowser";
+ UP: "UP.Browser";
+ VIVALDI: "Vivaldi";
+ VIVO: "Vivo Browser";
+ W3M: "w3m";
+ WATERFOX: "Waterfox";
+ WEBKIT: "WebKit";
+ WECHAT: "WeChat";
+ WEIBO: "Weibo";
+ WHALE: "Whale";
+ WOLVIC: "Wolvic";
+ YANDEX: "Yandex";
+ ZALO: "Zalo";
+}>;
+export const BrowserType: Readonly<{
+ CRAWLER: "crawler";
+ CLI: "cli";
+ EMAIL: "email";
+ FETCHER: "fetcher";
+ INAPP: "inapp";
+ MEDIAPLAYER: "mediaplayer";
+ LIBRARY: "library";
+}>;
+export const CPU: Readonly<{
+ '68K': "68k";
+ ALPHA: "alpha";
+ ARM: "arm";
+ ARM_64: "arm64";
+ ARM_HF: "armhf";
+ AVR: "avr";
+ AVR_32: "avr32";
+ IA64: "ia64";
+ IRIX: "irix";
+ IRIX_64: "irix64";
+ MIPS: "mips";
+ MIPS_64: "mips64";
+ PA_RISC: "pa-risc";
+ PPC: "ppc";
+ SPARC: "sparc";
+ SPARC_64: "sparc64";
+ X86: "ia32";
+ X86_64: "amd64";
+}>;
+export const Device: Readonly<{
+ CONSOLE: "console";
+ DESKTOP: "desktop";
+ EMBEDDED: "embedded";
+ MOBILE: "mobile";
+ SMARTTV: "smarttv";
+ TABLET: "tablet";
+ WEARABLE: "wearable";
+ XR: "xr";
+}>;
+export const Vendor: Readonly<{
+ ACER: "Acer";
+ ADVAN: "Advan";
+ ALCATEL: "Alcatel";
+ APPLE: "Apple";
+ AMAZON: "Amazon";
+ ARCHOS: "Archos";
+ ASUS: "ASUS";
+ ATT: "AT&T";
+ BENQ: "BenQ";
+ BLACKBERRY: "BlackBerry";
+ BLU: "BLU";
+ CAT: "Cat";
+ DELL: "Dell";
+ ENERGIZER: "Energizer";
+ ESSENTIAL: "Essential";
+ FACEBOOK: "Facebook";
+ FAIRPHONE: "Fairphone";
+ GEEKSPHONE: "GeeksPhone";
+ GENERIC: "Generic";
+ GOOGLE: "Google";
+ HMD: "HMD";
+ HP: "HP";
+ HTC: "HTC";
+ HUAWEI: "Huawei";
+ IMO: "IMO";
+ INFINIX: "Infinix";
+ ITEL: "itel";
+ JOLLA: "Jolla";
+ KOBO: "Kobo";
+ LAVA: "Lava";
+ LENOVO: "Lenovo";
+ LG: "LG";
+ MEIZU: "Meizu";
+ MICROMAX: "Micromax";
+ MICROSOFT: "Microsoft";
+ MOTOROLA: "Motorola";
+ NEXIAN: "Nexian";
+ NINTENDO: "Nintendo";
+ NOKIA: "Nokia";
+ NOTHING: "Nothing";
+ NVIDIA: "Nvidia";
+ ONEPLUS: "OnePlus";
+ OPPO: "OPPO";
+ OUYA: "Ouya";
+ PALM: "Palm";
+ PANASONIC: "Panasonic";
+ PEBBLE: "Pebble";
+ PHILIPS: "Philips";
+ PICO: "Pico";
+ POLYTRON: "Polytron";
+ REALME: "Realme";
+ RETROID: "Retroid";
+ RIM: "RIM";
+ ROKU: "Roku";
+ SAMSUNG: "Samsung";
+ SHARP: "Sharp";
+ SIEMENS: "Siemens";
+ SMARTFREN: "Smartfren";
+ SONY: "Sony";
+ SPRINT: "Sprint";
+ TCL: "TCL";
+ TECHNISAT: "TechniSAT";
+ TECNO: "Tecno";
+ TESLA: "Tesla";
+ ULEFONE: "Ulefone";
+ VIVO: "Vivo";
+ VIZIO: "Vizio";
+ VODAFONE: "Vodafone";
+ XBOX: "Xbox";
+ XIAOMI: "Xiaomi";
+ ZEBRA: "Zebra";
+ ZTE: "ZTE";
+}>;
+export const Engine: Readonly<{
+ AMAYA: "Amaya";
+ ARKWEB: "ArkWeb";
+ BLINK: "Blink";
+ EDGEHTML: "EdgeHTML";
+ FLOW: "Flow";
+ GECKO: "Gecko";
+ GOANNA: "Goanna";
+ ICAB: "iCab";
+ KHTML: "KHTML";
+ LIBWEB: "LibWeb";
+ LINKS: "Links";
+ LYNX: "Lynx";
+ NETFRONT: "NetFront";
+ NETSURF: "NetSurf";
+ PRESTO: "Presto";
+ SERVO: "Servo";
+ TASMAN: "Tasman";
+ TRIDENT: "Trident";
+ W3M: "w3m";
+ WEBKIT: "WebKit";
+}>;
+export const OS: Readonly<{
+ AIX: "AIX";
+ AMIGA_OS: "Amiga OS";
+ ANDROID: "Android";
+ ANDROID_X86: "Android-x86";
+ ARCAOS: "ArcaOS";
+ ARCH: "Arch";
+ BADA: "Bada";
+ BEOS: "BeOS";
+ BLACKBERRY: "BlackBerry";
+ CENTOS: "CentOS";
+ CHROME_OS: "Chrome OS";
+ CHROMECAST: "Chromecast";
+ CHROMECAST_ANDROID: "Chromecast Android";
+ CHROMECAST_FUCHSIA: "Chromecast Fuchsia";
+ CHROMECAST_LINUX: "Chromecast Linux";
+ CHROMECAST_SMARTSPEAKER: "Chromecast SmartSpeaker";
+ CONTIKI: "Contiki";
+ DEBIAN: "Debian";
+ DEEPIN: "Deepin";
+ DRAGONFLY: "DragonFly";
+ ELEMENTARY_OS: "elementary OS";
+ FEDORA: "Fedora";
+ FIREFOX_OS: "Firefox OS";
+ FREEBSD: "FreeBSD";
+ FUCHSIA: "Fuchsia";
+ GENTOO: "Gentoo";
+ GHOSTBSD: "GhostBSD";
+ GNU: "GNU";
+ HAIKU: "Haiku";
+ HARMONYOS: "HarmonyOS";
+ HP_UX: "HP-UX";
+ HURD: "Hurd";
+ IOS: "iOS";
+ JOLI: "Joli";
+ KAIOS: "KaiOS";
+ KNOPPIX: "Knoppix";
+ KUBUNTU: "Kubuntu";
+ LINPUS: "Linpus";
+ LINSPIRE: "Linspire";
+ LINUX: "Linux";
+ MACOS: "macOS";
+ MAEMO: "Maemo";
+ MAGEIA: "Mageia";
+ MANDRIVA: "Mandriva";
+ MANJARO: "Manjaro";
+ MEEGO: "MeeGo";
+ MINIX: "Minix";
+ MINT: "Mint";
+ MORPH_OS: "Morph OS";
+ NETBSD: "NetBSD";
+ NETRANGE: "NetRange";
+ NETTV: "NetTV";
+ NINTENDO: "Nintendo";
+ OPENHARMONY: "OpenHarmony";
+ OPENBSD: "OpenBSD";
+ OPENVMS: "OpenVMS";
+ OS2: "OS/2";
+ PALM: "Palm";
+ PC_BSD: "PC-BSD";
+ PCLINUXOS: "PCLinuxOS";
+ PICO: "Pico";
+ PLAN9: "Plan9";
+ PLAYSTATION: "PlayStation";
+ QNX: "QNX";
+ RASPBIAN: "Raspbian";
+ REDHAT: "RedHat";
+ RIM_TABLET_OS: "RIM Tablet OS";
+ RISC_OS: "RISC OS";
+ SABAYON: "Sabayon";
+ SAILFISH: "Sailfish";
+ SERENITYOS: "SerenityOS";
+ SERIES40: "Series40";
+ SLACKWARE: "Slackware";
+ SOLARIS: "Solaris";
+ SUSE: "SUSE";
+ SYMBIAN: "Symbian";
+ TIZEN: "Tizen";
+ UBUNTU: "Ubuntu";
+ UBUNTU_TOUCH: "Ubuntu Touch",
+ UNIX: "Unix";
+ VECTORLINUX: "VectorLinux";
+ WATCHOS: "watchOS";
+ WEBOS: "WebOS";
+ WINDOWS: "Windows";
+ WINDOWS_CE: "Windows CE";
+ WINDOWS_IOT: "Windows IoT";
+ WINDOWS_MOBILE: "Windows Mobile";
+ WINDOWS_PHONE: "Windows Phone";
+ WINDOWS_RT: "Windows RT";
+ XBOX: "Xbox";
+ XUBUNTU: "Xubuntu";
+ ZENWALK: "Zenwalk";
+}>;
diff --git a/src/enums/ua-parser-enums.js b/src/enums/ua-parser-enums.js
new file mode 100644
index 000000000..9aa28e6c3
--- /dev/null
+++ b/src/enums/ua-parser-enums.js
@@ -0,0 +1,414 @@
+///////////////////////////////////////////////
+/* Enums for UAParser.js v2.0.4
+ https://github.com/faisalman/ua-parser-js
+ Author: Faisal Salman
+ AGPLv3 License */
+//////////////////////////////////////////////
+
+/*jshint esversion: 6 */
+
+const Browser = Object.freeze({
+ '115': '115',
+ '2345': '2345',
+ '360': '360',
+ ALIPAY: 'Alipay',
+ AMAYA: 'Amaya',
+ ANDROID: 'Android Browser',
+ ARORA: 'Arora',
+ AVANT: 'Avant',
+ AVAST: 'Avast Secure Browser',
+ AVG: 'AVG Secure Browser',
+ BAIDU: 'Baidu Browser',
+ BASILISK: 'Basilisk',
+ BLAZER: 'Blazer',
+ BOLT: 'Bolt',
+ BOWSER: 'Bowser',
+ BRAVE: 'Brave',
+ CAMINO: 'Camino',
+ CHIMERA: 'Chimera',
+ CHROME: 'Chrome',
+ CHROME_HEADLESS: 'Chrome Headless',
+ CHROME_MOBILE: 'Mobile Chrome',
+ CHROME_WEBVIEW: 'Chrome WebView',
+ CHROMIUM: 'Chromium',
+ COBALT: 'Cobalt',
+ COC_COC: 'Coc Coc',
+ CONKEROR: 'Conkeror',
+ DAUM: 'Daum',
+ DILLO: 'Dillo',
+ DOLPHIN: 'Dolphin',
+ DOOBLE: 'Dooble',
+ DORIS: 'Doris',
+ DRAGON: 'Dragon',
+ DUCKDUCKGO: 'DuckDuckGo',
+ ECOSIA: 'Ecosia',
+ EDGE: 'Edge',
+ EDGE_WEBVIEW: 'Edge WebView',
+ EDGE_WEBVIEW2: 'Edge WebView2',
+ EPIPHANY: 'Epiphany',
+ FACEBOOK: 'Facebook',
+ FALKON: 'Falkon',
+ FIREBIRD: 'Firebird',
+ FIREFOX: 'Firefox',
+ FIREFOX_FOCUS: 'Firefox Focus',
+ FIREFOX_MOBILE: 'Mobile Firefox',
+ FIREFOX_REALITY: 'Firefox Reality',
+ FENNEC: 'Fennec',
+ FLOCK: 'Flock',
+ FLOW: 'Flow',
+ GO: 'GoBrowser',
+ GOOGLE_SEARCH: 'GSA',
+ HELIO: 'Helio',
+ HEYTAP: 'HeyTap',
+ HONOR: 'Honor',
+ HUAWEI: 'Huawei Browser',
+ ICAB: 'iCab',
+ ICE: 'ICE Browser',
+ ICEAPE: 'IceApe',
+ ICECAT: 'IceCat',
+ ICEDRAGON: 'IceDragon',
+ ICEWEASEL: 'IceWeasel',
+ IE: 'IE',
+ INSTAGRAM: 'Instagram',
+ IRIDIUM: 'Iridium',
+ IRON: 'Iron',
+ JASMINE: 'Jasmine',
+ KONQUEROR: 'Konqueror',
+ KAKAO: 'KakaoTalk',
+ KHTML: 'KHTML',
+ K_MELEON: 'K-Meleon',
+ KLAR: 'Klar',
+ KLARNA: 'Klarna',
+ KINDLE: 'Kindle',
+ LENOVO: 'Smart Lenovo Browser',
+ LADYBIRD: 'Ladybird',
+ LG: 'LG Browser',
+ LIBREWOLF: 'LibreWolf',
+ LIEBAO: 'LBBROWSER',
+ LINE: 'Line',
+ LINKEDIN: 'LinkedIn',
+ LINKS: 'Links',
+ LUNASCAPE: 'Lunascape',
+ LYNX: 'Lynx',
+ MAEMO: 'Maemo Browser',
+ MAXTHON: 'Maxthon',
+ MIDORI: 'Midori',
+ MINIMO: 'Minimo',
+ MIUI: 'MIUI Browser',
+ MOZILLA: 'Mozilla',
+ MOSAIC: 'Mosaic',
+ NAVER: 'Naver',
+ NETFRONT: 'NetFront',
+ NETSCAPE: 'Netscape',
+ NETSURF: 'Netsurf',
+ NOKIA: 'Nokia Browser',
+ OBIGO: 'Obigo',
+ OCULUS: 'Oculus Browser',
+ OMNIWEB: 'OmniWeb',
+ OPERA: 'Opera',
+ OPERA_COAST: 'Opera Coast',
+ OPERA_GX: 'Opera GX',
+ OPERA_MINI: 'Opera Mini',
+ OPERA_MOBI: 'Opera Mobi',
+ OPERA_TABLET: 'Opera Tablet',
+ OPERA_TOUCH: 'Opera Touch',
+ OTTER: 'Otter',
+ OVI: 'OviBrowser',
+ PALEMOON: 'PaleMoon',
+ PHANTOMJS: 'PhantomJS',
+ PHOENIX: 'Phoenix',
+ PICOBROWSER: 'Pico Browser',
+ POLARIS: 'Polaris',
+ PUFFIN: 'Puffin',
+ QQ: 'QQBrowser',
+ QQ_LITE: 'QQBrowserLite',
+ QUARK: 'Quark',
+ QUPZILLA: 'QupZilla',
+ QUTEBROWSER: 'qutebrowser',
+ REKONQ: 'rekonq',
+ ROCKMELT: 'Rockmelt',
+ SAFARI: 'Safari',
+ SAFARI_MOBILE: 'Mobile Safari',
+ SAILFISH: 'Sailfish Browser',
+ SAMSUNG: 'Samsung Internet',
+ SEAMONKEY: 'SeaMonkey',
+ SILK: 'Silk',
+ SKYFIRE: 'Skyfire',
+ SLEIPNIR: 'Sleipnir',
+ SLIMBOAT: 'SlimBoat',
+ SLIMBROWSER: 'SlimBrowser',
+ SLIMJET: 'Slimjet',
+ SNAPCHAT: 'Snapchat',
+ SOGOU_EXPLORER: 'Sogou Explorer',
+ SOGOU_MOBILE: 'Sogou Mobile',
+ SURF: 'Surf',
+ SWIFTFOX: 'Swiftfox',
+ TESLA: 'Tesla',
+ TIKTOK: 'TikTok',
+ TIZEN: 'Tizen Browser',
+ TWITTER: 'Twitter',
+ UC: 'UCBrowser',
+ UP: 'UP.Browser',
+ VIVALDI: 'Vivaldi',
+ VIVO: 'Vivo Browser',
+ W3M: 'w3m',
+ WATERFOX: 'Waterfox',
+ WEBKIT: 'WebKit',
+ WECHAT: 'WeChat',
+ WEIBO: 'Weibo',
+ WHALE: 'Whale',
+ WOLVIC: 'Wolvic',
+ YANDEX: 'Yandex',
+ ZALO: 'Zalo'
+
+ // TODO : test!
+});
+
+const BrowserType = Object.freeze({
+ CRAWLER: 'crawler',
+ CLI: 'cli',
+ EMAIL: 'email',
+ FETCHER: 'fetcher',
+ INAPP: 'inapp',
+ MEDIAPLAYER: 'mediaplayer',
+ LIBRARY: 'library'
+});
+
+const CPU = Object.freeze({
+ '68K': '68k',
+ ALPHA: 'alpha',
+ ARM : 'arm',
+ ARM_64: 'arm64',
+ ARM_HF: 'armhf',
+ AVR: 'avr',
+ AVR_32: 'avr32',
+ IA64: 'ia64',
+ IRIX: 'irix',
+ IRIX_64: 'irix64',
+ MIPS: 'mips',
+ MIPS_64: 'mips64',
+ PA_RISC: 'pa-risc',
+ PPC: 'ppc',
+ SPARC: 'sparc',
+ SPARC_64: 'sparc64',
+ X86: 'ia32',
+ X86_64: 'amd64'
+});
+
+const Device = Object.freeze({
+ CONSOLE: 'console',
+ DESKTOP: 'desktop',
+ EMBEDDED: 'embedded',
+ MOBILE: 'mobile',
+ SMARTTV: 'smarttv',
+ TABLET: 'tablet',
+ WEARABLE: 'wearable',
+ XR: 'xr'
+});
+
+const Vendor = Object.freeze({
+ ACER: 'Acer',
+ ADVAN: 'Advan',
+ ALCATEL: 'Alcatel',
+ APPLE: 'Apple',
+ AMAZON: 'Amazon',
+ ARCHOS: 'Archos',
+ ASUS: 'ASUS',
+ ATT: 'AT&T',
+ BENQ: 'BenQ',
+ BLACKBERRY: 'BlackBerry',
+ BLU: 'BLU',
+ CAT: 'Cat',
+ DELL: 'Dell',
+ ENERGIZER: 'Energizer',
+ ESSENTIAL: 'Essential',
+ FACEBOOK: 'Facebook',
+ FAIRPHONE: 'Fairphone',
+ GEEKSPHONE: 'GeeksPhone',
+ GENERIC: 'Generic',
+ GOOGLE: 'Google',
+ HMD: 'HMD',
+ HP: 'HP',
+ HTC: 'HTC',
+ HUAWEI: 'Huawei',
+ IMO: 'IMO',
+ INFINIX: 'Infinix',
+ ITEL: 'itel',
+ JOLLA: 'Jolla',
+ KOBO: 'Kobo',
+ LAVA: 'Lava',
+ LENOVO: 'Lenovo',
+ LG: 'LG',
+ MEIZU: 'Meizu',
+ MICROMAX: 'Micromax',
+ MICROSOFT: 'Microsoft',
+ MOTOROLA: 'Motorola',
+ NEXIAN: 'Nexian',
+ NINTENDO: 'Nintendo',
+ NOKIA: 'Nokia',
+ NOTHING: 'Nothing',
+ NVIDIA: 'Nvidia',
+ ONEPLUS: 'OnePlus',
+ OPPO: 'OPPO',
+ OUYA: 'Ouya',
+ PALM: 'Palm',
+ PANASONIC: 'Panasonic',
+ PEBBLE: 'Pebble',
+ PHILIPS: 'Philips',
+ PICO: 'Pico',
+ POLYTRON: 'Polytron',
+ REALME: 'Realme',
+ RETROID: 'Retroid',
+ RIM: 'RIM',
+ ROKU: 'Roku',
+ SAMSUNG: 'Samsung',
+ SHARP: 'Sharp',
+ SIEMENS: 'Siemens',
+ SMARTFREN: 'Smartfren',
+ SONY: 'Sony',
+ SPRINT: 'Sprint',
+ TCL: 'TCL',
+ TECHNISAT: 'TechniSAT',
+ TECNO: 'Tecno',
+ TESLA: 'Tesla',
+ ULEFONE: 'Ulefone',
+ VIVO: 'Vivo',
+ VIZIO: 'Vizio',
+ VODAFONE: 'Vodafone',
+ XBOX: 'Xbox',
+ XIAOMI: 'Xiaomi',
+ ZEBRA: 'Zebra',
+ ZTE: 'ZTE',
+
+ // TODO : test!
+});
+
+const Engine = Object.freeze({
+ AMAYA: 'Amaya',
+ ARKWEB: 'ArkWeb',
+ BLINK: 'Blink',
+ EDGEHTML: 'EdgeHTML',
+ FLOW: 'Flow',
+ GECKO: 'Gecko',
+ GOANNA: 'Goanna',
+ ICAB: 'iCab',
+ KHTML: 'KHTML',
+ LIBWEB: 'LibWeb',
+ LINKS: 'Links',
+ LYNX: 'Lynx',
+ NETFRONT: 'NetFront',
+ NETSURF: 'NetSurf',
+ PRESTO: 'Presto',
+ SERVO: 'Servo',
+ TASMAN: 'Tasman',
+ TRIDENT: 'Trident',
+ W3M: 'w3m',
+ WEBKIT: 'WebKit'
+});
+
+const OS = Object.freeze({
+ AIX: 'AIX',
+ AMIGA_OS: 'Amiga OS',
+ ANDROID: 'Android',
+ ANDROID_X86: 'Android-x86',
+ ARCAOS: 'ArcaOS',
+ ARCH: 'Arch',
+ BADA: 'Bada',
+ BEOS: 'BeOS',
+ BLACKBERRY: 'BlackBerry',
+ CENTOS: 'CentOS',
+ CHROME_OS: 'Chrome OS',
+ CHROMECAST: 'Chromecast',
+ CHROMECAST_ANDROID: 'Chromecast Android',
+ CHROMECAST_FUCHSIA: 'Chromecast Fuchsia',
+ CHROMECAST_LINUX: 'Chromecast Linux',
+ CHROMECAST_SMARTSPEAKER: 'Chromecast SmartSpeaker',
+ CONTIKI: 'Contiki',
+ DEBIAN: 'Debian',
+ DEEPIN: 'Deepin',
+ DRAGONFLY: 'DragonFly',
+ ELEMENTARY_OS: 'elementary OS',
+ FEDORA: 'Fedora',
+ FIREFOX_OS: 'Firefox OS',
+ FREEBSD: 'FreeBSD',
+ FUCHSIA: 'Fuchsia',
+ GENTOO: 'Gentoo',
+ GHOSTBSD: 'GhostBSD',
+ GNU: 'GNU',
+ HAIKU: 'Haiku',
+ HARMONYOS: 'HarmonyOS',
+ HP_UX: 'HP-UX',
+ HURD: 'Hurd',
+ IOS: 'iOS',
+ JOLI: 'Joli',
+ KAIOS: 'KaiOS',
+ KNOPPIX: 'Knoppix',
+ KUBUNTU: 'Kubuntu',
+ LINPUS: 'Linpus',
+ LINSPIRE: 'Linspire',
+ LINUX: 'Linux',
+ MACOS: 'macOS',
+ MAEMO: 'Maemo',
+ MAGEIA: 'Mageia',
+ MANDRIVA: 'Mandriva',
+ MANJARO: 'Manjaro',
+ MEEGO: 'MeeGo',
+ MINIX: 'Minix',
+ MINT: 'Mint',
+ MORPH_OS: 'Morph OS',
+ NETBSD: 'NetBSD',
+ NETRANGE: 'NetRange',
+ NETTV: 'NetTV',
+ NINTENDO: 'Nintendo',
+ OPENHARMONY: 'OpenHarmony',
+ OPENBSD: 'OpenBSD',
+ OPENVMS: 'OpenVMS',
+ OS2: 'OS/2',
+ PALM: 'Palm',
+ PC_BSD: 'PC-BSD',
+ PCLINUXOS: 'PCLinuxOS',
+ PICO: 'Pico',
+ PLAN9: 'Plan9',
+ PLAYSTATION: 'PlayStation',
+ QNX: 'QNX',
+ RASPBIAN: 'Raspbian',
+ REDHAT: 'RedHat',
+ RIM_TABLET_OS: 'RIM Tablet OS',
+ RISC_OS: 'RISC OS',
+ SABAYON: 'Sabayon',
+ SAILFISH: 'Sailfish',
+ SERENITYOS: 'SerenityOS',
+ SERIES40: 'Series40',
+ SLACKWARE: 'Slackware',
+ SOLARIS: 'Solaris',
+ SUSE: 'SUSE',
+ SYMBIAN: 'Symbian',
+ TIZEN: 'Tizen',
+ UBUNTU: 'Ubuntu',
+ UBUNTU_TOUCH: 'Ubuntu Touch',
+ UNIX: 'Unix',
+ VECTORLINUX: 'VectorLinux',
+ WATCHOS: 'watchOS',
+ WEBOS: 'WebOS',
+ WINDOWS: 'Windows',
+ WINDOWS_CE: 'Windows CE',
+ WINDOWS_IOT: 'Windows IoT',
+ WINDOWS_MOBILE: 'Windows Mobile',
+ WINDOWS_PHONE: 'Windows Phone',
+ WINDOWS_RT: 'Windows RT',
+ XBOX: 'Xbox',
+ XUBUNTU: 'Xubuntu',
+ ZENWALK: 'Zenwalk'
+
+ // TODO : test!
+});
+
+module.exports = {
+ Browser,
+ BrowserType,
+ CPU,
+ Device,
+ Vendor,
+ Engine,
+ OS
+};
\ No newline at end of file
diff --git a/src/enums/ua-parser-enums.mjs b/src/enums/ua-parser-enums.mjs
new file mode 100644
index 000000000..089af9573
--- /dev/null
+++ b/src/enums/ua-parser-enums.mjs
@@ -0,0 +1,415 @@
+// Generated ESM version of ua-parser-js/enums
+// DO NOT EDIT THIS FILE!
+// Source: /src/enums/ua-parser-enums.js
+
+///////////////////////////////////////////////
+/* Enums for UAParser.js v2.0.4
+ https://github.com/faisalman/ua-parser-js
+ Author: Faisal Salman
+ AGPLv3 License */
+//////////////////////////////////////////////
+
+/*jshint esversion: 6 */
+
+const Browser = Object.freeze({
+ '115': '115',
+ '2345': '2345',
+ '360': '360',
+ ALIPAY: 'Alipay',
+ AMAYA: 'Amaya',
+ ANDROID: 'Android Browser',
+ ARORA: 'Arora',
+ AVANT: 'Avant',
+ AVAST: 'Avast Secure Browser',
+ AVG: 'AVG Secure Browser',
+ BAIDU: 'Baidu Browser',
+ BASILISK: 'Basilisk',
+ BLAZER: 'Blazer',
+ BOLT: 'Bolt',
+ BOWSER: 'Bowser',
+ BRAVE: 'Brave',
+ CAMINO: 'Camino',
+ CHIMERA: 'Chimera',
+ CHROME: 'Chrome',
+ CHROME_HEADLESS: 'Chrome Headless',
+ CHROME_MOBILE: 'Mobile Chrome',
+ CHROME_WEBVIEW: 'Chrome WebView',
+ CHROMIUM: 'Chromium',
+ COBALT: 'Cobalt',
+ COC_COC: 'Coc Coc',
+ CONKEROR: 'Conkeror',
+ DAUM: 'Daum',
+ DILLO: 'Dillo',
+ DOLPHIN: 'Dolphin',
+ DOOBLE: 'Dooble',
+ DORIS: 'Doris',
+ DRAGON: 'Dragon',
+ DUCKDUCKGO: 'DuckDuckGo',
+ ECOSIA: 'Ecosia',
+ EDGE: 'Edge',
+ EDGE_WEBVIEW: 'Edge WebView',
+ EDGE_WEBVIEW2: 'Edge WebView2',
+ EPIPHANY: 'Epiphany',
+ FACEBOOK: 'Facebook',
+ FALKON: 'Falkon',
+ FIREBIRD: 'Firebird',
+ FIREFOX: 'Firefox',
+ FIREFOX_FOCUS: 'Firefox Focus',
+ FIREFOX_MOBILE: 'Mobile Firefox',
+ FIREFOX_REALITY: 'Firefox Reality',
+ FENNEC: 'Fennec',
+ FLOCK: 'Flock',
+ FLOW: 'Flow',
+ GO: 'GoBrowser',
+ GOOGLE_SEARCH: 'GSA',
+ HELIO: 'Helio',
+ HEYTAP: 'HeyTap',
+ HONOR: 'Honor',
+ HUAWEI: 'Huawei Browser',
+ ICAB: 'iCab',
+ ICE: 'ICE Browser',
+ ICEAPE: 'IceApe',
+ ICECAT: 'IceCat',
+ ICEDRAGON: 'IceDragon',
+ ICEWEASEL: 'IceWeasel',
+ IE: 'IE',
+ INSTAGRAM: 'Instagram',
+ IRIDIUM: 'Iridium',
+ IRON: 'Iron',
+ JASMINE: 'Jasmine',
+ KONQUEROR: 'Konqueror',
+ KAKAO: 'KakaoTalk',
+ KHTML: 'KHTML',
+ K_MELEON: 'K-Meleon',
+ KLAR: 'Klar',
+ KLARNA: 'Klarna',
+ KINDLE: 'Kindle',
+ LENOVO: 'Smart Lenovo Browser',
+ LADYBIRD: 'Ladybird',
+ LG: 'LG Browser',
+ LIBREWOLF: 'LibreWolf',
+ LIEBAO: 'LBBROWSER',
+ LINE: 'Line',
+ LINKEDIN: 'LinkedIn',
+ LINKS: 'Links',
+ LUNASCAPE: 'Lunascape',
+ LYNX: 'Lynx',
+ MAEMO: 'Maemo Browser',
+ MAXTHON: 'Maxthon',
+ MIDORI: 'Midori',
+ MINIMO: 'Minimo',
+ MIUI: 'MIUI Browser',
+ MOZILLA: 'Mozilla',
+ MOSAIC: 'Mosaic',
+ NAVER: 'Naver',
+ NETFRONT: 'NetFront',
+ NETSCAPE: 'Netscape',
+ NETSURF: 'Netsurf',
+ NOKIA: 'Nokia Browser',
+ OBIGO: 'Obigo',
+ OCULUS: 'Oculus Browser',
+ OMNIWEB: 'OmniWeb',
+ OPERA: 'Opera',
+ OPERA_COAST: 'Opera Coast',
+ OPERA_GX: 'Opera GX',
+ OPERA_MINI: 'Opera Mini',
+ OPERA_MOBI: 'Opera Mobi',
+ OPERA_TABLET: 'Opera Tablet',
+ OPERA_TOUCH: 'Opera Touch',
+ OTTER: 'Otter',
+ OVI: 'OviBrowser',
+ PALEMOON: 'PaleMoon',
+ PHANTOMJS: 'PhantomJS',
+ PHOENIX: 'Phoenix',
+ PICOBROWSER: 'Pico Browser',
+ POLARIS: 'Polaris',
+ PUFFIN: 'Puffin',
+ QQ: 'QQBrowser',
+ QQ_LITE: 'QQBrowserLite',
+ QUARK: 'Quark',
+ QUPZILLA: 'QupZilla',
+ QUTEBROWSER: 'qutebrowser',
+ REKONQ: 'rekonq',
+ ROCKMELT: 'Rockmelt',
+ SAFARI: 'Safari',
+ SAFARI_MOBILE: 'Mobile Safari',
+ SAILFISH: 'Sailfish Browser',
+ SAMSUNG: 'Samsung Internet',
+ SEAMONKEY: 'SeaMonkey',
+ SILK: 'Silk',
+ SKYFIRE: 'Skyfire',
+ SLEIPNIR: 'Sleipnir',
+ SLIMBOAT: 'SlimBoat',
+ SLIMBROWSER: 'SlimBrowser',
+ SLIMJET: 'Slimjet',
+ SNAPCHAT: 'Snapchat',
+ SOGOU_EXPLORER: 'Sogou Explorer',
+ SOGOU_MOBILE: 'Sogou Mobile',
+ SURF: 'Surf',
+ SWIFTFOX: 'Swiftfox',
+ TESLA: 'Tesla',
+ TIKTOK: 'TikTok',
+ TIZEN: 'Tizen Browser',
+ TWITTER: 'Twitter',
+ UC: 'UCBrowser',
+ UP: 'UP.Browser',
+ VIVALDI: 'Vivaldi',
+ VIVO: 'Vivo Browser',
+ W3M: 'w3m',
+ WATERFOX: 'Waterfox',
+ WEBKIT: 'WebKit',
+ WECHAT: 'WeChat',
+ WEIBO: 'Weibo',
+ WHALE: 'Whale',
+ WOLVIC: 'Wolvic',
+ YANDEX: 'Yandex'
+
+ // TODO : test!
+});
+
+const BrowserType = Object.freeze({
+ CRAWLER: 'crawler',
+ CLI: 'cli',
+ EMAIL: 'email',
+ FETCHER: 'fetcher',
+ INAPP: 'inapp',
+ MEDIAPLAYER: 'mediaplayer',
+ LIBRARY: 'library'
+});
+
+const CPU = Object.freeze({
+ '68K': '68k',
+ ARM : 'arm',
+ ARM_64: 'arm64',
+ ARM_HF: 'armhf',
+ AVR: 'avr',
+ AVR_32: 'avr32',
+ IA64: 'ia64',
+ IRIX: 'irix',
+ IRIX_64: 'irix64',
+ MIPS: 'mips',
+ MIPS_64: 'mips64',
+ PA_RISC: 'pa-risc',
+ PPC: 'ppc',
+ SPARC: 'sparc',
+ SPARC_64: 'sparc64',
+ X86: 'ia32',
+ X86_64: 'amd64'
+});
+
+const Device = Object.freeze({
+ CONSOLE: 'console',
+ DESKTOP: 'desktop',
+ EMBEDDED: 'embedded',
+ MOBILE: 'mobile',
+ SMARTTV: 'smarttv',
+ TABLET: 'tablet',
+ WEARABLE: 'wearable',
+ XR: 'xr'
+});
+
+const Vendor = Object.freeze({
+ ACER: 'Acer',
+ ADVAN: 'Advan',
+ ALCATEL: 'Alcatel',
+ APPLE: 'Apple',
+ AMAZON: 'Amazon',
+ ARCHOS: 'Archos',
+ ASUS: 'ASUS',
+ ATT: 'AT&T',
+ BENQ: 'BenQ',
+ BLACKBERRY: 'BlackBerry',
+ BLU: 'BLU',
+ CAT: 'Cat',
+ DELL: 'Dell',
+ ENERGIZER: 'Energizer',
+ ESSENTIAL: 'Essential',
+ FACEBOOK: 'Facebook',
+ FAIRPHONE: 'Fairphone',
+ GEEKSPHONE: 'GeeksPhone',
+ GENERIC: 'Generic',
+ GOOGLE: 'Google',
+ HMD: 'HMD',
+ HP: 'HP',
+ HTC: 'HTC',
+ HUAWEI: 'Huawei',
+ IMO: 'IMO',
+ INFINIX: 'Infinix',
+ ITEL: 'itel',
+ JOLLA: 'Jolla',
+ KOBO: 'Kobo',
+ LAVA: 'Lava',
+ LENOVO: 'Lenovo',
+ LG: 'LG',
+ MEIZU: 'Meizu',
+ MICROMAX: 'Micromax',
+ MICROSOFT: 'Microsoft',
+ MOTOROLA: 'Motorola',
+ NEXIAN: 'Nexian',
+ NINTENDO: 'Nintendo',
+ NOKIA: 'Nokia',
+ NOTHING: 'Nothing',
+ NVIDIA: 'Nvidia',
+ ONEPLUS: 'OnePlus',
+ OPPO: 'OPPO',
+ OUYA: 'Ouya',
+ PALM: 'Palm',
+ PANASONIC: 'Panasonic',
+ PEBBLE: 'Pebble',
+ PICO: 'Pico',
+ POLYTRON: 'Polytron',
+ REALME: 'Realme',
+ RETROID: 'Retroid',
+ RIM: 'RIM',
+ ROKU: 'Roku',
+ SAMSUNG: 'Samsung',
+ SHARP: 'Sharp',
+ SIEMENS: 'Siemens',
+ SMARTFREN: 'Smartfren',
+ SONY: 'Sony',
+ SPRINT: 'Sprint',
+ TCL: 'TCL',
+ TECHNISAT: 'TechniSAT',
+ TECNO: 'Tecno',
+ TESLA: 'Tesla',
+ ULEFONE: 'Ulefone',
+ VIVO: 'Vivo',
+ VIZIO: 'Vizio',
+ VODAFONE: 'Vodafone',
+ XBOX: 'Xbox',
+ XIAOMI: 'Xiaomi',
+ ZEBRA: 'Zebra',
+ ZTE: 'ZTE',
+
+ // TODO : test!
+});
+
+const Engine = Object.freeze({
+ AMAYA: 'Amaya',
+ ARKWEB: 'ArkWeb',
+ BLINK: 'Blink',
+ EDGEHTML: 'EdgeHTML',
+ FLOW: 'Flow',
+ GECKO: 'Gecko',
+ GOANNA: 'Goanna',
+ ICAB: 'iCab',
+ KHTML: 'KHTML',
+ LIBWEB: 'LibWeb',
+ LINKS: 'Links',
+ LYNX: 'Lynx',
+ NETFRONT: 'NetFront',
+ NETSURF: 'NetSurf',
+ PRESTO: 'Presto',
+ SERVO: 'Servo',
+ TASMAN: 'Tasman',
+ TRIDENT: 'Trident',
+ W3M: 'w3m',
+ WEBKIT: 'WebKit'
+});
+
+const OS = Object.freeze({
+ AIX: 'AIX',
+ AMIGA_OS: 'Amiga OS',
+ ANDROID: 'Android',
+ ANDROID_X86: 'Android-x86',
+ ARCAOS: 'ArcaOS',
+ ARCH: 'Arch',
+ BADA: 'Bada',
+ BEOS: 'BeOS',
+ BLACKBERRY: 'BlackBerry',
+ CENTOS: 'CentOS',
+ CHROME_OS: 'Chrome OS',
+ CHROMECAST: 'Chromecast',
+ CHROMECAST_ANDROID: 'Chromecast Android',
+ CHROMECAST_FUCHSIA: 'Chromecast Fuchsia',
+ CHROMECAST_LINUX: 'Chromecast Linux',
+ CHROMECAST_SMARTSPEAKER: 'Chromecast SmartSpeaker',
+ CONTIKI: 'Contiki',
+ DEBIAN: 'Debian',
+ DEEPIN: 'Deepin',
+ DRAGONFLY: 'DragonFly',
+ ELEMENTARY_OS: 'elementary OS',
+ FEDORA: 'Fedora',
+ FIREFOX_OS: 'Firefox OS',
+ FREEBSD: 'FreeBSD',
+ FUCHSIA: 'Fuchsia',
+ GENTOO: 'Gentoo',
+ GHOSTBSD: 'GhostBSD',
+ GNU: 'GNU',
+ HAIKU: 'Haiku',
+ HARMONYOS: 'HarmonyOS',
+ HP_UX: 'HP-UX',
+ HURD: 'Hurd',
+ IOS: 'iOS',
+ JOLI: 'Joli',
+ KAIOS: 'KaiOS',
+ KNOPPIX: 'Knoppix',
+ KUBUNTU: 'Kubuntu',
+ LINPUS: 'Linpus',
+ LINSPIRE: 'Linspire',
+ LINUX: 'Linux',
+ MACOS: 'macOS',
+ MAEMO: 'Maemo',
+ MAGEIA: 'Mageia',
+ MANDRIVA: 'Mandriva',
+ MANJARO: 'Manjaro',
+ MEEGO: 'MeeGo',
+ MINIX: 'Minix',
+ MINT: 'Mint',
+ MORPH_OS: 'Morph OS',
+ NETBSD: 'NetBSD',
+ NETRANGE: 'NetRange',
+ NETTV: 'NetTV',
+ NINTENDO: 'Nintendo',
+ OPENHARMONY: 'OpenHarmony',
+ OPENBSD: 'OpenBSD',
+ OPENVMS: 'OpenVMS',
+ OS2: 'OS/2',
+ PALM: 'Palm',
+ PC_BSD: 'PC-BSD',
+ PCLINUXOS: 'PCLinuxOS',
+ PICO: 'Pico',
+ PLAN9: 'Plan9',
+ PLAYSTATION: 'PlayStation',
+ QNX: 'QNX',
+ RASPBIAN: 'Raspbian',
+ REDHAT: 'RedHat',
+ RIM_TABLET_OS: 'RIM Tablet OS',
+ RISC_OS: 'RISC OS',
+ SABAYON: 'Sabayon',
+ SAILFISH: 'Sailfish',
+ SERENITYOS: 'SerenityOS',
+ SERIES40: 'Series40',
+ SLACKWARE: 'Slackware',
+ SOLARIS: 'Solaris',
+ SUSE: 'SUSE',
+ SYMBIAN: 'Symbian',
+ TIZEN: 'Tizen',
+ UBUNTU: 'Ubuntu',
+ UBUNTU_TOUCH: 'Ubuntu Touch',
+ UNIX: 'Unix',
+ VECTORLINUX: 'VectorLinux',
+ WATCHOS: 'watchOS',
+ WEBOS: 'WebOS',
+ WINDOWS: 'Windows',
+ WINDOWS_CE: 'Windows CE',
+ WINDOWS_IOT: 'Windows IoT',
+ WINDOWS_MOBILE: 'Windows Mobile',
+ WINDOWS_PHONE: 'Windows Phone',
+ WINDOWS_RT: 'Windows RT',
+ XBOX: 'Xbox',
+ XUBUNTU: 'Xubuntu',
+ ZENWALK: 'Zenwalk'
+
+ // TODO : test!
+});
+
+export {
+ Browser,
+ BrowserType,
+ CPU,
+ Device,
+ Vendor,
+ Engine,
+ OS
+};
\ No newline at end of file
diff --git a/src/extensions/ua-parser-extensions.d.ts b/src/extensions/ua-parser-extensions.d.ts
new file mode 100644
index 000000000..4ee31dd86
--- /dev/null
+++ b/src/extensions/ua-parser-extensions.d.ts
@@ -0,0 +1,16 @@
+// Type definitions for Helpers submodule of UAParser.js v2.0.4
+// Project: https://github.com/faisalman/ua-parser-js
+// Definitions by: Faisal Salman
+
+import type { UAParserExt } from "../main/ua-parser";
+
+export const Bots: UAParserExt;
+export const CLIs: UAParserExt;
+export const Crawlers: UAParserExt;
+export const ExtraDevices: UAParserExt;
+export const Emails: UAParserExt;
+export const Fetchers: UAParserExt;
+export const InApps: UAParserExt;
+export const Libraries: UAParserExt;
+export const MediaPlayers: UAParserExt;
+export const Vehicles: UAParserExt;
\ No newline at end of file
diff --git a/src/extensions/ua-parser-extensions.js b/src/extensions/ua-parser-extensions.js
new file mode 100644
index 000000000..4e93b00fe
--- /dev/null
+++ b/src/extensions/ua-parser-extensions.js
@@ -0,0 +1,435 @@
+///////////////////////////////////////////////
+/* Extensions for UAParser.js v2.0.4
+ https://github.com/faisalman/ua-parser-js
+ Author: Faisal Salman
+ AGPLv3 License */
+//////////////////////////////////////////////
+
+/*jshint esversion: 6 */
+
+const MODEL = 'model';
+const NAME = 'name';
+const TYPE = 'type';
+const VENDOR = 'vendor';
+const VERSION = 'version';
+const MOBILE = 'mobile';
+const TABLET = 'tablet';
+const CRAWLER = 'crawler';
+const CLI = 'cli';
+const EMAIL = 'email';
+const FETCHER = 'fetcher';
+const INAPP = 'inapp';
+const MEDIAPLAYER = 'mediaplayer';
+const LIBRARY = 'library';
+
+//////////////////////
+// COMMAND LINE APPS
+/////////////////////
+
+const CLIs = Object.freeze({
+ browser : [
+ // wget / curl / Lynx / ELinks / HTTPie
+ [/(wget|curl|lynx|elinks|httpie)[\/ ]\(?([\w\.-]+)/i], [NAME, VERSION, [TYPE, CLI]]
+ ]
+});
+
+////////////////////////
+// CRAWLERS / SPIDERS
+///////////////////////
+
+const Crawlers = Object.freeze({
+ browser : [
+ [
+ // AhrefsBot - https://ahrefs.com/robot
+ // Amazonbot - https://developer.amazon.com/amazonbot
+ // Bingbot / AdIdxBot - https://www.bing.com/webmasters/help/which-crawlers-does-bing-use-8c184ec0
+ // Bravebot - https://search.brave.com/help/brave-search-crawler
+ // CCBot - https://commoncrawl.org/faq
+ // Coveobot - https://connect.coveo.com/s/article/19648
+ // CriteoBot - https://www.criteo.com/criteo-crawler/
+ // Dotbot - https://moz.com/help/moz-procedures/crawlers/dotbot
+ // DuckDuckBot - http://duckduckgo.com/duckduckbot.html
+ // FacebookBot - https://developers.facebook.com/docs/sharing/bot/
+ // GPTBot - https://platform.openai.com/docs/gptbot
+ // iAskBot - https://iask.ai
+ // Kangaroo Bot - https://kangaroollm.com.au/kangaroo-bot/
+ // LinkedInBot - http://www.linkedin.com
+ // MJ12bot - https://mj12bot.com/
+ // MojeekBot - https://www.mojeek.com/bot.html
+ // Onespot - https://www.onespot.com/identifying-traffic.html
+ // OpenAI's SearchGPT - https://platform.openai.com/docs/bots
+ // PerplexityBot - https://perplexity.ai/perplexitybot
+ // SeznamBot - http://napoveda.seznam.cz/seznambot-intro
+ /((?:adidx|ahrefs|amazon|bing|brave|cc|coveo|criteo|dot|duckduck(?:go-favicons-)?|exa|facebook|gpt|iask|kangaroo |linkedin|mj12|mojeek|oai-search|onespot-scraper|perplexity|semrush|seznam)bot)\/([\w\.-]+)/i,
+
+ // Applebot - http://apple.com/go/applebot
+ /(applebot(?:-extended)?)\/?([\w\.]*)/i,
+
+ // Baiduspider https://help.baidu.com/question?prod_id=99&class=0&id=3001
+ /(baiduspider[-imagevdonwsfcpr]{0,7})\/?([\w\.]*)/i,
+
+ // ClaudeBot (Anthropic)
+ /(claude(?:bot|-searchbot|-web)|anthropic-ai)\/?([\w\.]*)/i,
+
+ // Coc Coc Bot - https://help.coccoc.com/en/search-engine
+ /(coccocbot-(?:image|web))\/([\w\.]+)/i,
+
+ // Daum
+ /(daum(?:oa)?(?:-image)?)[ \/]([\w\.]+)/i,
+
+ // Facebook / Meta
+ // https://developers.facebook.com/docs/sharing/webmasters/web-crawlers
+ /(facebook(?:externalhit|catalog)|meta-externalagent)\/([\w\.]+)/i,
+
+ // Googlebot - http://www.google.com/bot.html
+ /(google(?:bot|other|-inspectiontool)(?:-image|-video|-news)?|storebot-google)\/?([\w\.]*)/i,
+
+ // Internet Archive (archive.org)
+ /(ia_archiver|archive\.org_bot)\/?([\w\.]*)/i,
+
+ // Qwantbot - https://help.qwant.com/bot
+ /(qwantbot)[-\w]*\/?([\w\.]*)/i,
+
+ // SemrushBot - http://www.semrush.com/bot.html
+ /((?:semrush|splitsignal)bot[-abcfimostw]*)\/?([\w\.-]*)/i,
+
+ // Sogou Spider
+ /(sogou (?:pic|head|web|orion|news) spider)\/([\w\.]+)/i,
+
+ // Yahoo! Japan - https://support.yahoo-net.jp/PccSearch/s/article/H000007955
+ /(y!?j-(?:asr|br[uw]|dscv|mmp|vsidx|wsc))\/([\w\.]+)/i,
+
+ // Yandex Bots - https://yandex.com/bots
+ /(yandex(?:(?:mobile)?(?:accessibility|additional|renderresources|screenshot|sprav)?bot|image(?:s|resizer)|video(?:parser)?|blogs|adnet|favicons|fordomain|market|media|metrika|news|ontodb(?:api)?|pagechecker|partner|rca|tracker|turbo|vertis|webmaster|antivirus))\/([\w\.]+)/i,
+
+ // Yeti (Naver)
+ /(yeti)\/([\w\.]+)/i,
+
+ // aiHitBot / Diffbot / FirecrawlAgent / HuggingFace-Bot / Linespider / Magpie-Crawler / Omgilibot / OpenAI Image Downloader / PanguBot / Replicate-Bot / RunPod-Bot / Webzio-Extended / Screaming Frog SEO Spider / Startpage / Timpibot / Together-Bot / VelenPublicWebCrawler / xAI-Bot / YisouSpider / YouBot
+ // Cotoyogi - https://ds.rois.ac.jp/en_center8/en_crawler/
+ /((?:aihit|diff|huggingface-|pangu|replicate-|runpod-|timpi|together-|xai-|you)bot|omgili(?:bot)?|cotoyogi|firecrawlagent|openai image downloader|(?:magpie-|velenpublicweb)crawler|startpageprivateimageproxy|webzio-extended|(?:chatglm-|line|screaming frog seo |yisou)spider)\/?([\w\.]*)/i
+ ],
+
+ [NAME, VERSION, [TYPE, CRAWLER]],
+
+ [
+ // Google Bots
+ /((?:adsbot|apis|mediapartners)-google(?:-mobile)?|google-?(?:other|cloudvertexbot|extended|safety))/i,
+
+ // AI2Bot - https://allenai.org/crawler
+ // Bytespider
+ // DataForSeoBot - https://dataforseo.com/dataforseo-bot
+ // DeepSeekBot
+ // Huawei AspiegelBot / PetalBot https://aspiegel.com/petalbot
+ // ImagesiftBot - https://imagesift.com/about
+ // Qihoo 360Spider
+ // TurnitinBot - https://www.turnitin.com/robot/crawlerinfo.html
+ // v0bot - https://vercel.com/docs/bot-management
+ // Yahoo! Slurp - http://help.yahoo.com/help/us/ysearch/slurp
+ /\b(360spider-?(?:image|video)?|bytespider|cohere-training-data-crawler|elastic(?=\/s)|(?:ai2|aspiegel|dataforseo|deepseek|imagesift|petal|turnitin|v0)bot|teoma|yahoo! slurp)/i
+ ],
+ [NAME, [TYPE, CRAWLER]]
+ ]
+});
+
+//////////////////
+// EXTRA DEVICES
+/////////////////
+
+const ExtraDevices = Object.freeze({
+ device : [[
+ /(nook)[\w ]+build\/(\w+)/i, // Nook
+ /(dell) (strea[kpr\d ]*[\dko])/i, // Dell Streak
+ /(le[- ]+pan)[- ]+(\w{1,9}) bui/i, // Le Pan Tablets
+ /(trinity)[- ]*(t\d{3}) bui/i, // Trinity Tablets
+ /(gigaset)[- ]+(q\w{1,9}) bui/i, // Gigaset Tablets
+ /(vodafone) ([\w ]+)(?:\)| bui)/i // Vodafone
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+ /(u304aa)/i // AT&T
+ ], [MODEL, [VENDOR, 'AT&T'], [TYPE, MOBILE]], [
+
+ /\bsie-(\w*)/i // Siemens
+ ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [
+
+ /\b(rct\w+) b/i // RCA Tablets
+ ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [
+
+ /\b(venue[\d ]{2,7}) b/i // Dell Venue Tablets
+ ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [
+
+ /\b(q(?:mv|ta)\w+) b/i // Verizon Tablet
+ ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [
+
+ /\b(?:barnes[& ]+noble |bn[rt])([\w\+ ]*) b/i // Barnes & Noble Tablet
+ ], [MODEL, [VENDOR, 'Barnes & Noble'], [TYPE, TABLET]], [
+
+ /\b(tm\d{3}\w+) b/i
+ ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [
+
+ /\b(k88) b/i // ZTE K Series Tablet
+ ], [MODEL, [VENDOR, 'ZTE'], [TYPE, TABLET]], [
+
+ /\b(nx\d{3}j) b/i // ZTE Nubia
+ ], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [
+
+ /\b(gen\d{3}) b.+49h/i // Swiss GEN Mobile
+ ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [
+
+ /\b(zur\d{3}) b/i // Swiss ZUR Tablet
+ ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [
+
+ /^((zeki)?tb.*\b) b/i // Zeki Tablets
+ ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [
+
+ /\b([yr]\d{2}) b/i,
+ /\b(?:dragon[- ]+touch |dt)(\w{5}) b/i // Dragon Touch Tablet
+ ], [MODEL, [VENDOR, 'Dragon Touch'], [TYPE, TABLET]], [
+
+ /\b(ns-?\w{0,9}) b/i // Insignia Tablets
+ ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [
+
+ /\b((nxa|next)-?\w{0,9}) b/i // NextBook Tablets
+ ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [
+
+ /\b(xtreme\_)?(v(1[045]|2[015]|[3469]0|7[05])) b/i // Voice Xtreme Phones
+ ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [
+
+ /\b(lvtel\-)?(v1[12]) b/i // LvTel Phones
+ ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [
+
+ /\b(ph-1) /i // Essential PH-1
+ ], [MODEL, [VENDOR, 'Essential'], [TYPE, MOBILE]], [
+
+ /\b(v(100md|700na|7011|917g).*\b) b/i // Envizen Tablets
+ ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [
+
+ /\b(trio[-\w\. ]+) b/i // MachSpeed Tablets
+ ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [
+
+ /\btu_(1491) b/i // Rotor Tablets
+ ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]]
+ ]
+});
+
+///////////////
+// EMAIL APPS
+//////////////
+
+const Emails = Object.freeze({
+ browser : [
+ [
+ // 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,
+
+ // Apple's Mail
+ /(mail)\/([\w\.]+) cf/i
+ ], [NAME, VERSION, [TYPE, EMAIL]], [
+
+ // Zimbra
+ /zdesktop\/([\w\.]+)/i
+ ], [VERSION, [NAME, 'Zimbra'], [TYPE, EMAIL]]
+ ]
+});
+
+///////////////////////
+// ON-DEMAND SCRAPERS
+//////////////////////
+
+const Fetchers = Object.freeze({
+ browser : [
+ [
+ // Asana / Bitlybot / Better Uptime / BingPreview / Blueno / kakaotalk-scrap / Mastodon / MicrosoftPreview / Pinterestbot / Redditbot / Rogerbot / SiteAuditBot / Telegrambot / Twitterbot / UptimeRobot
+ // 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
+ // DuckAssistBot - https://duckduckgo.com/duckassistbot/
+ // Google Site Verifier / Meta / Yahoo! Japan
+ // Iframely - https://iframely.com/docs/about
+ // 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|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)?|searchshop)|yadirectfetcher)\/([\w\.]+)/i,
+
+ // Bluesky
+ /(bluesky) cardyb\/([\w\.]+)/i,
+
+ // Skype
+ /(skypeuripreview) preview\/([\w\.]+)/i,
+
+ // Slackbot - https://api.slack.com/robots
+ /(slack(?:bot)?(?:-imgproxy|-linkexpanding)?) ([\w\.]+)/i,
+
+ // WhatsApp
+ /(whatsapp)\/([\w\.]+)/i
+ ],
+ [NAME, VERSION, [TYPE, FETCHER]],
+
+ [
+ // Google Bots / Chrome-Lighthouse / Cohere / Gemini-Deep-Research / Snapchat / TikTokSpider / Vercelbot / Yandex Bots
+ /((?:better uptime |telegram|vercel)bot|chrome-lighthouse|cohere-ai|feedfetcher-google|gemini-deep-research|google(?:imageproxy|-read-aloud|-pagerenderer|producer)|snap url preview|tiktokspider|vercel(flags|tracing|-(favicon|screenshot)-bot)|yandex(?:sitelinks|userproxy))/i
+ ],
+ [NAME, [TYPE, FETCHER]],
+ ],
+
+ os : [
+ [/whatsapp\/[\d\.]+ (a|i)/i],
+ [[NAME, os => os == 'A' ? 'Android' : 'iOS' ]]
+ ]
+});
+
+////////////////////
+// IN-APP BROWSERS
+///////////////////
+
+const InApps = Object.freeze({
+ browser : [[
+ // Discord/Figma/Flipboard/Mattermost/Notion/Postman/Rambox/Rocket.Chat/Slack/Teams
+ /\b(discord|figma|mattermost|notion|postman|rambox|rocket.chat|slack|teams)\/([\w\.]+).+(electron\/|; ios)/i,
+ /(flipboard)\/([\w\.]+)/i
+ ], [NAME, VERSION, [TYPE, INAPP]], [
+
+ // Evernote/Teams on mobile
+ /(evernote) win/i,
+ /(teams)mobile-(ios|and)/i
+ ], [NAME, [TYPE, INAPP]], [
+
+ // Slack
+ /chatlyio\/([\d\.]+)/i],
+ [VERSION, [NAME, 'Slack'], [TYPE, INAPP]], [
+
+ // TikTok Lite
+ /ultralite app_version\/([\w\.]+)/i],
+ [VERSION, [NAME, 'TikTok Lite'], [TYPE, INAPP]], [
+
+ // VS Code
+ /\) code\/([\d\.]+).+electron\//i],
+ [VERSION, [NAME, 'VS Code'], [TYPE, INAPP]], [
+
+ // Yahoo! Japan
+ /jp\.co\.yahoo\.(?:android\.yjtop|ipn\.appli)\/([\d\.]+)/i],
+ [VERSION, [NAME, 'Yahoo! Japan'], [TYPE, INAPP]]
+ ]
+});
+
+//////////////////////
+// MEDIA PLAYER APPS
+/////////////////////
+
+const MediaPlayers = Object.freeze({
+ browser : [[
+ /(apple(?:coremedia|tv))\/([\w\._]+)/i, // Generic Apple CoreMedia
+ /(coremedia) v([\w\._]+)/i,
+ // Ares/Nexplayer/OSSProxy
+ /(ares|clementine|music player daemon|nexplayer|ossproxy) ([\w\.-]+)/i,
+ // Aqualung/Lyssna/BSPlayer/Clementine/MPD
+ // Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/GnomeMplayer/MoC
+ // NSPlayer/PSP-InternetRadioPlayer/Videos
+ // Nero Home/Nero Scout/Nokia
+ // QuickTime/RealMedia/RadioApp/RadioClientApplication/
+ // SoundTap/Totem/Stagefright/Streamium
+ // XBMC/gvfs/Xine/XMMS/irapp
+ /^(aqualung|audacious|audimusicstream|amarok|bass|bsplayer|core|gnomemplayer|gvfs|irapp|lyssna|music on console|nero (?:home|scout)|nokia\d+|nsplayer|psp-internetradioplayer|quicktime|rma|radioapp|radioclientapplication|soundtap|stagefright|streamium|totem|videos|xbmc|xine|xmms)\/([\w\.-]+)/i,
+ /(lg player|nexplayer) ([\d\.]+)/i,
+ /player\/(nexplayer|lg player) ([\w\.-]+)/i, // NexPlayer/LG Player
+ /(gstreamer) souphttpsrc.+libsoup\/([\w\.-]+)/i, // Gstreamer
+ /(htc streaming player) [\w_]+ \/ ([\d\.]+)/i, // HTC Streaming Player
+ /(lavf)([\d\.]+)/i, // Lavf (FFMPEG)
+ // MPlayer SVN
+ /(mplayer)(?: |\/)(?:(?:sherpya-){0,1}svn)(?:-| )(r\d+(?:-\d+[\w\.-]+))/i,
+ / (songbird)\/([\w\.-]+)/i, // Songbird/Philips-Songbird
+ /(winamp)(?:3 version|mpeg| ) ([\w\.-]+)/i, // Winamp
+ /(vlc)(?:\/| media player - version )([\w\.-]+)/i, // VLC Videolan
+ /^(foobar2000|itunes|smp)\/([\d\.]+)/i, // Foobar2000/iTunes/SMP
+ /com\.(riseupradioalarm)\/([\d\.]*)/i, // RiseUP Radio Alarm
+ /(mplayer)(?:\s|\/| unknown-)([\w\.\-]+)/i, // MPlayer
+ // Windows Media Server
+ /(windows)\/([\w\.-]+) upnp\/[\d\.]+ dlnadoc\/[\d\.]+ home media server/i
+ ], [NAME, VERSION, [TYPE, MEDIAPLAYER]], [
+
+ /(flrp)\/([\w\.-]+)/i // Flip Player
+ ], [[NAME, 'Flip Player'], VERSION, [TYPE, MEDIAPLAYER]], [
+ // FStream/NativeHost/QuerySeekSpider
+ // MPlayer (no other info)/Media Player Classic/Nero ShowTime
+ // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info)
+ // inlight radio / YourMuze
+ /(fstream|media player classic|inlight radio|mplayer|nativehost|nero showtime|ocms-bot|queryseekspider|tapinradio|tunein radio|winamp|yourmuze)/i
+ ], [NAME, [TYPE, MEDIAPLAYER]], [
+
+ /(htc_one_s|windows-media-player|wmplayer)\/([\w\.-]+)/i, // HTC One S / Windows Media Player
+ ], [[NAME, /[_-]/g, ' '], VERSION, [TYPE, MEDIAPLAYER]], [
+
+ /(rad.io|radio.(?:de|at|fr)) ([\d\.]+)/i // Rad.io
+ ], [[NAME, 'rad.io'], VERSION, [TYPE, MEDIAPLAYER]]
+ ]
+});
+
+/////////////
+// LIBRARIES
+//////////////
+
+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,
+ /(adobeair|aiohttp|jsdom)\/([\w\.]+)/i,
+ /(nutch)-([\w\.-]+)(\(|$)/i,
+ /\((java)\/([\w\.]+)/i
+ ], [NAME, VERSION, [TYPE, LIBRARY]]
+ ]
+});
+
+/////////////
+// VEHICLES
+////////////
+
+const Vehicles = Object.freeze({
+ device : [
+ [/aftlbt962e2/i], // BMW
+ [[VENDOR, 'BMW']],
+
+ [/dilink.+(byd) auto/i], // BYD
+ [VENDOR],
+
+ [/aftlft962x3/i], // Jeep
+ [[VENDOR, 'Jeep'], [MODEL, 'Wagooner']],
+
+ [/(rivian) (r1t)/i], // Rivian
+ [VENDOR, MODEL],
+
+ [/vcc.+netfront/i], // Volvo
+ [[VENDOR, 'Volvo']]
+ ]
+});
+
+//////////
+// BOTS
+/////////
+
+const Bots = Object.freeze({
+ browser : [
+ ...CLIs.browser,
+ ...Fetchers.browser,
+ ...Crawlers.browser,
+ ...Libraries.browser
+ ],
+ os : [
+ ...Fetchers.os
+ ]
+});
+
+module.exports = {
+ Bots,
+ CLIs,
+ Crawlers,
+ ExtraDevices,
+ Emails,
+ Fetchers,
+ InApps,
+ Libraries,
+ MediaPlayers,
+ Vehicles
+};
\ No newline at end of file
diff --git a/src/extensions/ua-parser-extensions.mjs b/src/extensions/ua-parser-extensions.mjs
new file mode 100644
index 000000000..9401493e4
--- /dev/null
+++ b/src/extensions/ua-parser-extensions.mjs
@@ -0,0 +1,431 @@
+// Generated ESM version of ua-parser-js/extensions
+// DO NOT EDIT THIS FILE!
+// Source: /src/extensions/ua-parser-extensions.js
+
+///////////////////////////////////////////////
+/* Extensions for UAParser.js v2.0.4
+ https://github.com/faisalman/ua-parser-js
+ Author: Faisal Salman
+ AGPLv3 License */
+//////////////////////////////////////////////
+
+/*jshint esversion: 6 */
+
+const MODEL = 'model';
+const NAME = 'name';
+const TYPE = 'type';
+const VENDOR = 'vendor';
+const VERSION = 'version';
+const MOBILE = 'mobile';
+const TABLET = 'tablet';
+const CRAWLER = 'crawler';
+const CLI = 'cli';
+const EMAIL = 'email';
+const FETCHER = 'fetcher';
+const INAPP = 'inapp';
+const MEDIAPLAYER = 'mediaplayer';
+const LIBRARY = 'library';
+
+//////////////////////
+// COMMAND LINE APPS
+/////////////////////
+
+const CLIs = Object.freeze({
+ browser : [
+ // wget / curl / Lynx / ELinks / HTTPie
+ [/(wget|curl|lynx|elinks|httpie)[\/ ]\(?([\w\.-]+)/i], [NAME, VERSION, [TYPE, CLI]]
+ ]
+});
+
+////////////////////////
+// CRAWLERS / SPIDERS
+///////////////////////
+
+const Crawlers = Object.freeze({
+ browser : [
+ [
+ // AhrefsBot - https://ahrefs.com/robot
+ // Amazonbot - https://developer.amazon.com/amazonbot
+ // Bingbot / AdIdxBot - https://www.bing.com/webmasters/help/which-crawlers-does-bing-use-8c184ec0
+ // CCBot - https://commoncrawl.org/faq
+ // Dotbot - https://moz.com/help/moz-procedures/crawlers/dotbot
+ // DuckDuckBot - http://duckduckgo.com/duckduckbot.html
+ // FacebookBot - https://developers.facebook.com/docs/sharing/bot/
+ // GPTBot - https://platform.openai.com/docs/gptbot
+ // iAskBot - https://iask.ai
+ // LinkedInBot - http://www.linkedin.com
+ // MJ12bot - https://mj12bot.com/
+ // MojeekBot - https://www.mojeek.com/bot.html
+ // Onespot - https://www.onespot.com/identifying-traffic.html
+ // OpenAI's SearchGPT - https://platform.openai.com/docs/bots
+ // PerplexityBot - https://perplexity.ai/perplexitybot
+ // SeznamBot - http://napoveda.seznam.cz/seznambot-intro
+ /((?:adidx|ahrefs|amazon|bing|cc|dot|duckduck|exa|facebook|gpt|iask|linkedin|mj12|mojeek|oai-search|onespot-scraper|perplexity|semrush|seznam)bot)\/([\w\.-]+)/i,
+
+ // Applebot - http://apple.com/go/applebot
+ /(applebot(?:-extended)?)\/?([\w\.]*)/i,
+
+ // Baiduspider https://help.baidu.com/question?prod_id=99&class=0&id=3001
+ /(baiduspider[-imagevdonwsfcpr]{0,7})\/?([\w\.]*)/i,
+
+ // ClaudeBot (Anthropic)
+ /(claude(?:bot|-web)|anthropic-ai)\/?([\w\.]*)/i,
+
+ // Coc Coc Bot - https://help.coccoc.com/en/search-engine
+ /(coccocbot-(?:image|web))\/([\w\.]+)/i,
+
+ // Daum
+ /(daum(?:oa)?(?:-image)?)[ \/]([\w\.]+)/i,
+
+ // Facebook / Meta
+ // https://developers.facebook.com/docs/sharing/webmasters/web-crawlers
+ /(facebook(?:externalhit|catalog)|meta-externalagent)\/([\w\.]+)/i,
+
+ // Googlebot - http://www.google.com/bot.html
+ /(google(?:bot|other|-inspectiontool)(?:-image|-video|-news)?|storebot-google)\/?([\w\.]*)/i,
+
+ // Internet Archive (archive.org)
+ /(ia_archiver|archive\.org_bot)\/?([\w\.]*)/i,
+
+ // Qwantbot - https://help.qwant.com/bot
+ /(qwantbot)[-\w]*\/?([\w\.]*)/i,
+
+ // SemrushBot - http://www.semrush.com/bot.html
+ /((?:semrush|splitsignal)bot[-abcfimostw]*)\/?([\w\.-]*)/i,
+
+ // Sogou Spider
+ /(sogou (?:pic|head|web|orion|news) spider)\/([\w\.]+)/i,
+
+ // Yahoo! Japan - https://support.yahoo-net.jp/PccSearch/s/article/H000007955
+ /(y!?j-(?:asr|br[uw]|dscv|mmp|vsidx|wsc))\/([\w\.]+)/i,
+
+ // Yandex Bots - https://yandex.com/bots
+ /(yandex(?:(?:mobile)?(?:accessibility|additional|renderresources|screenshot|sprav)?bot|image(?:s|resizer)|video(?:parser)?|blogs|adnet|favicons|fordomain|market|media|metrika|news|ontodb(?:api)?|pagechecker|partner|rca|tracker|turbo|vertis|webmaster|antivirus))\/([\w\.]+)/i,
+
+ // Yeti (Naver)
+ /(yeti)\/([\w\.]+)/i,
+
+ // aiHitBot / Diffbot / Linespider / Magpie-Crawler / Omgilibot / OpenAI Image Downloader / Webzio-Extended / Screaming Frog SEO Spider / Startpage / Timpibot / VelenPublicWebCrawler / YisouSpider / YouBot
+ /((?:aihit|diff|timpi|you)bot|omgili(?:bot)?|openai image downloader|(?:magpie-|velenpublicweb)crawler|startpageprivateimageproxy|webzio-extended|(?:chatglm-|line|screaming frog seo |yisou)spider)\/?([\w\.]*)/i
+ ],
+
+ [NAME, VERSION, [TYPE, CRAWLER]],
+
+ [
+ // Google Bots
+ /((?:adsbot|apis|mediapartners)-google(?:-mobile)?|google-?(?:other|cloudvertexbot|extended|safety))/i,
+
+ // AI2Bot - https://allenai.org/crawler
+ // Bytespider
+ // DataForSeoBot - https://dataforseo.com/dataforseo-bot
+ // Huawei AspiegelBot / PetalBot https://aspiegel.com/petalbot
+ // ImagesiftBot - https://imagesift.com/about
+ // Qihoo 360Spider
+ // TurnitinBot - https://www.turnitin.com/robot/crawlerinfo.html
+ // Yahoo! Slurp - http://help.yahoo.com/help/us/ysearch/slurp
+ /\b(360spider-?(?:image|video)?|bytespider|(?:ai2|aspiegel|dataforseo|imagesift|petal|turnitin)bot|teoma|yahoo! slurp)/i
+ ],
+ [NAME, [TYPE, CRAWLER]]
+ ]
+});
+
+//////////////////
+// EXTRA DEVICES
+/////////////////
+
+const ExtraDevices = Object.freeze({
+ device : [[
+ /(nook)[\w ]+build\/(\w+)/i, // Nook
+ /(dell) (strea[kpr\d ]*[\dko])/i, // Dell Streak
+ /(le[- ]+pan)[- ]+(\w{1,9}) bui/i, // Le Pan Tablets
+ /(trinity)[- ]*(t\d{3}) bui/i, // Trinity Tablets
+ /(gigaset)[- ]+(q\w{1,9}) bui/i, // Gigaset Tablets
+ /(vodafone) ([\w ]+)(?:\)| bui)/i // Vodafone
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+ /(u304aa)/i // AT&T
+ ], [MODEL, [VENDOR, 'AT&T'], [TYPE, MOBILE]], [
+
+ /\bsie-(\w*)/i // Siemens
+ ], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [
+
+ /\b(rct\w+) b/i // RCA Tablets
+ ], [MODEL, [VENDOR, 'RCA'], [TYPE, TABLET]], [
+
+ /\b(venue[\d ]{2,7}) b/i // Dell Venue Tablets
+ ], [MODEL, [VENDOR, 'Dell'], [TYPE, TABLET]], [
+
+ /\b(q(?:mv|ta)\w+) b/i // Verizon Tablet
+ ], [MODEL, [VENDOR, 'Verizon'], [TYPE, TABLET]], [
+
+ /\b(?:barnes[& ]+noble |bn[rt])([\w\+ ]*) b/i // Barnes & Noble Tablet
+ ], [MODEL, [VENDOR, 'Barnes & Noble'], [TYPE, TABLET]], [
+
+ /\b(tm\d{3}\w+) b/i
+ ], [MODEL, [VENDOR, 'NuVision'], [TYPE, TABLET]], [
+
+ /\b(k88) b/i // ZTE K Series Tablet
+ ], [MODEL, [VENDOR, 'ZTE'], [TYPE, TABLET]], [
+
+ /\b(nx\d{3}j) b/i // ZTE Nubia
+ ], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [
+
+ /\b(gen\d{3}) b.+49h/i // Swiss GEN Mobile
+ ], [MODEL, [VENDOR, 'Swiss'], [TYPE, MOBILE]], [
+
+ /\b(zur\d{3}) b/i // Swiss ZUR Tablet
+ ], [MODEL, [VENDOR, 'Swiss'], [TYPE, TABLET]], [
+
+ /^((zeki)?tb.*\b) b/i // Zeki Tablets
+ ], [MODEL, [VENDOR, 'Zeki'], [TYPE, TABLET]], [
+
+ /\b([yr]\d{2}) b/i,
+ /\b(?:dragon[- ]+touch |dt)(\w{5}) b/i // Dragon Touch Tablet
+ ], [MODEL, [VENDOR, 'Dragon Touch'], [TYPE, TABLET]], [
+
+ /\b(ns-?\w{0,9}) b/i // Insignia Tablets
+ ], [MODEL, [VENDOR, 'Insignia'], [TYPE, TABLET]], [
+
+ /\b((nxa|next)-?\w{0,9}) b/i // NextBook Tablets
+ ], [MODEL, [VENDOR, 'NextBook'], [TYPE, TABLET]], [
+
+ /\b(xtreme\_)?(v(1[045]|2[015]|[3469]0|7[05])) b/i // Voice Xtreme Phones
+ ], [[VENDOR, 'Voice'], MODEL, [TYPE, MOBILE]], [
+
+ /\b(lvtel\-)?(v1[12]) b/i // LvTel Phones
+ ], [[VENDOR, 'LvTel'], MODEL, [TYPE, MOBILE]], [
+
+ /\b(ph-1) /i // Essential PH-1
+ ], [MODEL, [VENDOR, 'Essential'], [TYPE, MOBILE]], [
+
+ /\b(v(100md|700na|7011|917g).*\b) b/i // Envizen Tablets
+ ], [MODEL, [VENDOR, 'Envizen'], [TYPE, TABLET]], [
+
+ /\b(trio[-\w\. ]+) b/i // MachSpeed Tablets
+ ], [MODEL, [VENDOR, 'MachSpeed'], [TYPE, TABLET]], [
+
+ /\btu_(1491) b/i // Rotor Tablets
+ ], [MODEL, [VENDOR, 'Rotor'], [TYPE, TABLET]]
+ ]
+});
+
+///////////////
+// EMAIL APPS
+//////////////
+
+const Emails = Object.freeze({
+ browser : [
+ [
+ // 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,
+
+ // Apple's Mail
+ /(mail)\/([\w\.]+) cf/i
+ ], [NAME, VERSION, [TYPE, EMAIL]], [
+
+ // Zimbra
+ /zdesktop\/([\w\.]+)/i
+ ], [VERSION, [NAME, 'Zimbra'], [TYPE, EMAIL]]
+ ]
+});
+
+///////////////////////
+// ON-DEMAND SCRAPERS
+//////////////////////
+
+const Fetchers = Object.freeze({
+ browser : [
+ [
+ // AhrefsSiteAudit - https://ahrefs.com/robot/site-audit
+ // ChatGPT-User - https://platform.openai.com/docs/plugins/bot
+ // DuckAssistBot - https://duckduckgo.com/duckassistbot/
+ // Better Uptime / BingPreview / Mastodon / MicrosoftPreview / Pinterestbot / Redditbot / Rogerbot / SiteAuditBot / Telegrambot / Twitterbot / UptimeRobot
+ // Google Site Verifier / Meta / Yahoo! Japan
+ // Iframely - https://iframely.com/docs/about
+ // Perplexity-User - https://docs.perplexity.ai/guides/bots
+ // MistralAI-User - https://docs.mistral.ai/robots/
+ // Yandex Bots - https://yandex.com/bots
+ /(ahrefssiteaudit|(?:bing|microsoft)preview|(?:chatgpt|mistralai|perplexity)-user|mastodon|(?:discord|duckassist|linkedin|pinterest|reddit|roger|siteaudit|twitter|uptimero)bot|google-site-verification|iframely|meta-externalfetcher|y!?j-dlc|yandex(?:calendar|direct(?:dyn)?|searchshop)|yadirectfetcher)\/([\w\.]+)/i,
+
+ // Bluesky
+ /(bluesky) cardyb\/([\w\.]+)/i,
+
+ // Skype
+ /(skypeuripreview) preview\/([\w\.]+)/i,
+
+ // Slackbot - https://api.slack.com/robots
+ /(slack(?:bot)?(?:-imgproxy|-linkexpanding)?) ([\w\.]+)/i,
+
+ // WhatsApp
+ /(whatsapp)\/([\w\.]+)/i
+ ],
+ [NAME, VERSION, [TYPE, FETCHER]],
+
+ [
+ // Google Bots / Cohere / Snapchat / Vercelbot / Yandex Bots
+ /((?:better uptime |telegram|vercel)bot|cohere-ai|feedfetcher-google|google(?:imageproxy|-read-aloud|-pagerenderer|producer)|snap url preview|yandex(?:sitelinks|userproxy))/i
+ ],
+ [NAME, [TYPE, FETCHER]],
+ ],
+
+ os : [
+ [/whatsapp\/[\d\.]+ (a|i)/i],
+ [[NAME, os => os == 'A' ? 'Android' : 'iOS' ]]
+ ]
+});
+
+////////////////////
+// IN-APP BROWSERS
+///////////////////
+
+const InApps = Object.freeze({
+ browser : [[
+ // Discord/Figma/Flipboard/Mattermost/Notion/Postman/Rambox/Rocket.Chat/Slack/Teams
+ /\b(discord|figma|mattermost|notion|postman|rambox|rocket.chat|slack|teams)\/([\w\.]+).+(electron\/|; ios)/i,
+ /(flipboard)\/([\w\.]+)/i
+ ], [NAME, VERSION, [TYPE, INAPP]], [
+
+ // Evernote/Teams on mobile
+ /(evernote) win/i,
+ /(teams)mobile-(ios|and)/i
+ ], [NAME, [TYPE, INAPP]], [
+
+ // Slack
+ /chatlyio\/([\d\.]+)/i],
+ [VERSION, [NAME, 'Slack'], [TYPE, INAPP]], [
+
+ // TikTok Lite
+ /ultralite app_version\/([\w\.]+)/i],
+ [VERSION, [NAME, 'TikTok Lite'], [TYPE, INAPP]], [
+
+ // VS Code
+ /\) code\/([\d\.]+).+electron\//i],
+ [VERSION, [NAME, 'VS Code'], [TYPE, INAPP]], [
+
+ // Yahoo! Japan
+ /jp\.co\.yahoo\.(?:android\.yjtop|ipn\.appli)\/([\d\.]+)/i],
+ [VERSION, [NAME, 'Yahoo! Japan'], [TYPE, INAPP]]
+ ]
+});
+
+//////////////////////
+// MEDIA PLAYER APPS
+/////////////////////
+
+const MediaPlayers = Object.freeze({
+ browser : [[
+ /(apple(?:coremedia|tv))\/([\w\._]+)/i, // Generic Apple CoreMedia
+ /(coremedia) v([\w\._]+)/i,
+ // Ares/Nexplayer/OSSProxy
+ /(ares|clementine|music player daemon|nexplayer|ossproxy) ([\w\.-]+)/i,
+ // Aqualung/Lyssna/BSPlayer/Clementine/MPD
+ // Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/GnomeMplayer/MoC
+ // NSPlayer/PSP-InternetRadioPlayer/Videos
+ // Nero Home/Nero Scout/Nokia
+ // QuickTime/RealMedia/RadioApp/RadioClientApplication/
+ // SoundTap/Totem/Stagefright/Streamium
+ // XBMC/gvfs/Xine/XMMS/irapp
+ /^(aqualung|audacious|audimusicstream|amarok|bass|bsplayer|core|gnomemplayer|gvfs|irapp|lyssna|music on console|nero (?:home|scout)|nokia\d+|nsplayer|psp-internetradioplayer|quicktime|rma|radioapp|radioclientapplication|soundtap|stagefright|streamium|totem|videos|xbmc|xine|xmms)\/([\w\.-]+)/i,
+ /(lg player|nexplayer) ([\d\.]+)/i,
+ /player\/(nexplayer|lg player) ([\w\.-]+)/i, // NexPlayer/LG Player
+ /(gstreamer) souphttpsrc.+libsoup\/([\w\.-]+)/i, // Gstreamer
+ /(htc streaming player) [\w_]+ \/ ([\d\.]+)/i, // HTC Streaming Player
+ /(lavf)([\d\.]+)/i, // Lavf (FFMPEG)
+ // MPlayer SVN
+ /(mplayer)(?: |\/)(?:(?:sherpya-){0,1}svn)(?:-| )(r\d+(?:-\d+[\w\.-]+))/i,
+ / (songbird)\/([\w\.-]+)/i, // Songbird/Philips-Songbird
+ /(winamp)(?:3 version|mpeg| ) ([\w\.-]+)/i, // Winamp
+ /(vlc)(?:\/| media player - version )([\w\.-]+)/i, // VLC Videolan
+ /^(foobar2000|itunes|smp)\/([\d\.]+)/i, // Foobar2000/iTunes/SMP
+ /com\.(riseupradioalarm)\/([\d\.]*)/i, // RiseUP Radio Alarm
+ /(mplayer)(?:\s|\/| unknown-)([\w\.\-]+)/i, // MPlayer
+ // Windows Media Server
+ /(windows)\/([\w\.-]+) upnp\/[\d\.]+ dlnadoc\/[\d\.]+ home media server/i
+ ], [NAME, VERSION, [TYPE, MEDIAPLAYER]], [
+
+ /(flrp)\/([\w\.-]+)/i // Flip Player
+ ], [[NAME, 'Flip Player'], VERSION, [TYPE, MEDIAPLAYER]], [
+ // FStream/NativeHost/QuerySeekSpider
+ // MPlayer (no other info)/Media Player Classic/Nero ShowTime
+ // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info)
+ // inlight radio / YourMuze
+ /(fstream|media player classic|inlight radio|mplayer|nativehost|nero showtime|ocms-bot|queryseekspider|tapinradio|tunein radio|winamp|yourmuze)/i
+ ], [NAME, [TYPE, MEDIAPLAYER]], [
+
+ /(htc_one_s|windows-media-player|wmplayer)\/([\w\.-]+)/i, // HTC One S / Windows Media Player
+ ], [[NAME, /[_-]/g, ' '], VERSION, [TYPE, MEDIAPLAYER]], [
+
+ /(rad.io|radio.(?:de|at|fr)) ([\d\.]+)/i // Rad.io
+ ], [[NAME, 'rad.io'], VERSION, [TYPE, MEDIAPLAYER]]
+ ]
+});
+
+/////////////
+// LIBRARIES
+//////////////
+
+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,
+ /(adobeair|aiohttp|jsdom)\/([\w\.]+)/i,
+ /(nutch)-([\w\.-]+)(\(|$)/i,
+ /\((java)\/([\w\.]+)/i
+ ], [NAME, VERSION, [TYPE, LIBRARY]]
+ ]
+});
+
+/////////////
+// VEHICLES
+////////////
+
+const Vehicles = Object.freeze({
+ device : [
+ [/aftlbt962e2/i], // BMW
+ [[VENDOR, 'BMW']],
+
+ [/dilink.+(byd) auto/i], // BYD
+ [VENDOR],
+
+ [/aftlft962x3/i], // Jeep
+ [[VENDOR, 'Jeep'], [MODEL, 'Wagooner']],
+
+ [/(rivian) (r1t)/i], // Rivian
+ [VENDOR, MODEL],
+
+ [/vcc.+netfront/i], // Volvo
+ [[VENDOR, 'Volvo']]
+ ]
+});
+
+//////////
+// BOTS
+/////////
+
+const Bots = Object.freeze({
+ browser : [
+ ...CLIs.browser,
+ ...Crawlers.browser,
+ ...Fetchers.browser,
+ ...Libraries.browser
+ ],
+ os : [
+ ...Fetchers.os
+ ]
+});
+
+export {
+ Bots,
+ CLIs,
+ Crawlers,
+ ExtraDevices,
+ Emails,
+ Fetchers,
+ InApps,
+ Libraries,
+ MediaPlayers,
+ Vehicles
+};
\ No newline at end of file
diff --git a/src/helpers/ua-parser-helpers.d.ts b/src/helpers/ua-parser-helpers.d.ts
new file mode 100644
index 000000000..fc53c614f
--- /dev/null
+++ b/src/helpers/ua-parser-helpers.d.ts
@@ -0,0 +1,27 @@
+// Type definitions for Helpers submodule of UAParser.js v2.0.4
+// Project: https://github.com/faisalman/ua-parser-js
+// Definitions by: Faisal Salman
+
+import type { IResult } from "../main/ua-parser";
+
+declare function getDeviceVendor(model: string): string | undefined;
+declare function isAppleSilicon(resultOrUA: IResult | string): boolean;
+declare function isAIBot(resultOrUA: IResult | string): boolean;
+declare function isBot(resultOrUA: IResult | string): boolean;
+declare function isChromeFamily(resultOrUA: IResult | string): boolean;
+declare function isElectron(): boolean;
+declare function isFromEU(): boolean;
+declare function isFrozenUA(ua: string): boolean;
+declare function isStandalonePWA(): boolean;
+
+export {
+ getDeviceVendor,
+ isAppleSilicon,
+ isAIBot,
+ isBot,
+ isChromeFamily,
+ isElectron,
+ isFromEU,
+ isFrozenUA,
+ isStandalonePWA
+}
\ No newline at end of file
diff --git a/src/helpers/ua-parser-helpers.js b/src/helpers/ua-parser-helpers.js
new file mode 100644
index 000000000..912ccdb53
--- /dev/null
+++ b/src/helpers/ua-parser-helpers.js
@@ -0,0 +1,182 @@
+///////////////////////////////////////////////
+/* Helpers for UAParser.js v2.0.4
+ https://github.com/faisalman/ua-parser-js
+ Author: Faisal Salman
+ AGPLv3 License */
+//////////////////////////////////////////////
+
+/*jshint esversion: 6 */
+
+const { UAParser } = require('../main/ua-parser');
+const { CPU, OS, Engine } = require('../enums/ua-parser-enums');
+const { Bots } = require('../extensions/ua-parser-extensions');
+const { isFromEU } = require('detect-europe-js');
+const { isFrozenUA } = require('ua-is-frozen');
+const { isStandalonePWA } = require('is-standalone-pwa');
+
+const toResult = (value, head, ext) => typeof value === 'string' ? UAParser(value, head, ext) : value;
+
+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;
+
+const isAppleSilicon = (resultOrUA) => {
+ const res = toResult(resultOrUA);
+ if (res.os.is(OS.MACOS)) {
+ if (res.cpu.is(CPU.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;
+ }
+ }
+ }
+ return false;
+}
+
+const isAIBot = (resultOrUA) => [
+
+ // AI2
+ 'ai2bot',
+
+ // Amazon
+ 'amazonbot',
+
+ // Anthropic
+ 'anthropic-ai',
+ 'claude-web',
+ 'claude-searchbot',
+ 'claudebot',
+
+ // Apple
+ 'applebot',
+ 'applebot-extended',
+
+ // Brave
+ 'bravebot',
+
+ // ByteDance
+ 'bytespider',
+ 'tiktokspider',
+
+ // Cohere
+ 'cohere-training-data-crawler',
+
+ // Common Crawl
+ 'ccbot',
+
+ // Coveo
+ 'coveobot',
+
+ // DataForSeo
+ 'dataforseobot',
+
+ // DeepSeek
+ 'deepseekbot',
+
+ // Diffbot
+ 'diffbot',
+
+ // Google
+ 'googleother',
+ 'googleother-image',
+ 'googleother-video',
+ 'google-extended',
+
+ // Hive AI
+ 'imagesiftbot',
+
+ // Huawei
+ 'petalbot',
+ 'pangubot',
+
+ // Hugging Face
+ 'huggingface-bot',
+
+ // Kangaroo
+ 'kangaroo bot',
+
+ // Mendable.ai
+ 'firecrawlagent',
+
+ // Meta
+ 'facebookbot',
+ 'meta-externalagent',
+
+ // OpenAI
+ 'gptbot',
+ 'oai-searchbot',
+
+ // Perplexity
+ 'perplexitybot',
+
+ // Replicate
+ 'replicate-bot',
+
+ // Runpod
+ 'runpod-bot',
+
+ // Semrush
+ 'semrushbot-ocob',
+
+ // Timpi
+ 'timpibot',
+
+ // Together AI
+ 'together-bot',
+
+ // Velen.io
+ 'velenpublicwebcrawler',
+
+ // Vercel
+ 'v0bot',
+
+ // Webz.io
+ 'omgili',
+ 'omgilibot',
+ 'webzio-extended',
+
+ // X
+ 'xai-bot',
+
+ // You.com
+ 'youbot',
+
+ // Zhipu AI
+ 'chatglm-spider',
+
+ // Zyte
+ 'scrapy'
+
+ ].includes(String(toResult(resultOrUA, Bots).browser.name).toLowerCase());
+
+const isBot = (resultOrUA) => [
+ 'cli',
+ 'crawler',
+ 'fetcher',
+ 'library'
+ ].includes(toResult(resultOrUA, Bots).browser.type);
+
+const isChromeFamily = (resultOrUA) => toResult(resultOrUA).engine.is(Engine.BLINK);
+
+const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
+ / electron\//i.test(navigator?.userAgent)); // browser
+
+module.exports = {
+ getDeviceVendor,
+ isAppleSilicon,
+ isAIBot,
+ isBot,
+ isChromeFamily,
+ isElectron,
+ isFromEU,
+ isFrozenUA,
+ isStandalonePWA
+}
\ No newline at end of file
diff --git a/src/helpers/ua-parser-helpers.mjs b/src/helpers/ua-parser-helpers.mjs
new file mode 100644
index 000000000..3530e7dbc
--- /dev/null
+++ b/src/helpers/ua-parser-helpers.mjs
@@ -0,0 +1,147 @@
+// Generated ESM version of ua-parser-js/helpers
+// DO NOT EDIT THIS FILE!
+// Source: /src/helpers/ua-parser-helpers.js
+
+///////////////////////////////////////////////
+/* Helpers for UAParser.js v2.0.4
+ https://github.com/faisalman/ua-parser-js
+ Author: Faisal Salman
+ AGPLv3 License */
+//////////////////////////////////////////////
+
+/*jshint esversion: 6 */
+
+import { UAParser } from '../main/ua-parser.mjs';
+import { CPU, OS, Engine } from '../enums/ua-parser-enums.mjs';
+import { Bots } from '../extensions/ua-parser-extensions.mjs';
+import { isFromEU } from 'detect-europe-js';
+import { isFrozenUA } from 'ua-is-frozen';
+import { isStandalonePWA } from 'is-standalone-pwa';
+
+const toResult = (value, head, ext) => typeof value === 'string' ? UAParser(value, head, ext) : value;
+
+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;
+
+const isAppleSilicon = (resultOrUA) => {
+ const res = toResult(resultOrUA);
+ if (res.os.is(OS.MACOS)) {
+ if (res.cpu.is(CPU.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;
+ }
+ }
+ }
+ return false;
+}
+
+const isAIBot = (resultOrUA) => [
+
+ // AI2
+ 'ai2bot',
+
+ // Amazon
+ 'amazonbot',
+
+ // Anthropic
+ 'anthropic-ai',
+ 'claude-web',
+ 'claudebot',
+
+ // Apple
+ 'applebot',
+ 'applebot-extended',
+
+ // ByteDance
+ 'bytespider',
+
+ // Common Crawl
+ 'ccbot',
+
+ // DataForSeo
+ 'dataforseobot',
+
+ // Diffbot
+ 'diffbot',
+
+ // Google
+ 'googleother',
+ 'googleother-image',
+ 'googleother-video',
+ 'google-extended',
+
+ // Hive AI
+ 'imagesiftbot',
+
+ // Huawei
+ 'petalbot',
+
+ // Meta
+ 'facebookbot',
+ 'meta-externalagent',
+
+ // OpenAI
+ 'gptbot',
+ 'oai-searchbot',
+
+ // Perplexity
+ 'perplexitybot',
+
+ // Semrush
+ 'semrushbot-ocob',
+
+ // Timpi
+ 'timpibot',
+
+ // Velen.io
+ 'velenpublicwebcrawler',
+
+ // Webz.io
+ 'omgili',
+ 'omgilibot',
+ 'webzio-extended',
+
+ // You.com
+ 'youbot',
+
+ // Zhipu AI
+ 'chatglm-spider',
+
+ // Zyte
+ 'scrapy'
+
+ ].includes(String(toResult(resultOrUA, Bots).browser.name).toLowerCase());
+
+const isBot = (resultOrUA) => [
+ 'cli',
+ 'crawler',
+ 'fetcher',
+ 'library'
+ ].includes(toResult(resultOrUA, Bots).browser.type);
+
+const isChromeFamily = (resultOrUA) => toResult(resultOrUA).engine.is(Engine.BLINK);
+
+const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
+ / electron\//i.test(navigator?.userAgent)); // browser
+
+export {
+ getDeviceVendor,
+ isAppleSilicon,
+ isAIBot,
+ isBot,
+ isChromeFamily,
+ isElectron,
+ isFromEU,
+ isFrozenUA,
+ isStandalonePWA
+}
\ No newline at end of file
diff --git a/src/main/ua-parser.d.ts b/src/main/ua-parser.d.ts
new file mode 100644
index 000000000..d1acf4bf5
--- /dev/null
+++ b/src/main/ua-parser.d.ts
@@ -0,0 +1,113 @@
+// Type definitions for UAParser.js v2.0.4
+// Project: https://github.com/faisalman/ua-parser-js
+// Definitions by: Faisal Salman
+
+import type { Headers } from "undici";
+import type { IncomingHttpHeaders } from "undici/types/header";
+
+declare namespace UAParser {
+
+ interface IData {
+ is(val: string): boolean;
+ toString(): string;
+ withClientHints(): PromiseLike | T;
+ withFeatureCheck(): PromiseLike | T;
+ }
+
+ interface IBrowser extends IData {
+ name?: string;
+ version?: string;
+ major?: string;
+ type?: 'crawler' | 'cli' | 'email' | 'fetcher' | 'inapp' | 'mediaplayer' | 'library';
+ }
+
+ interface ICPU extends IData {
+ architecture?: 'ia32' | 'ia64' | 'amd64' | 'arm' | 'arm64' | 'armhf' | 'avr' | 'avr32' | 'irix' | 'irix64' | 'mips' | 'mips64' | '68k' | 'pa-risc' | 'ppc' | 'sparc' | 'sparc64';
+ }
+
+ interface IDevice extends IData {
+ type?: 'mobile' | 'tablet' | 'console' | 'smarttv' | 'wearable' | 'xr' | 'embedded';
+ vendor?: string;
+ model?: string;
+ }
+
+ interface IEngine extends IData {
+ name?: 'Amaya' | 'ArkWeb' | 'Blink' | 'EdgeHTML' | 'Flow' | 'Gecko' | 'Goanna' | 'iCab' | 'KHTML' | 'LibWeb' | 'Links' | 'Lynx' | 'NetFront' | 'NetSurf' | 'Presto' | 'Servo' | 'Tasman' | 'Trident' | 'w3m' | 'WebKit';
+ version?: string;
+ }
+
+ interface IOS extends IData {
+ name?: string;
+ version?: string;
+ }
+
+ interface IResult extends IData {
+ ua: string;
+ browser: IBrowser;
+ cpu: ICPU;
+ device: IDevice;
+ engine: IEngine;
+ os: IOS;
+ }
+
+ type RegexMap = ((RegExp | string | (string | RegExp | Function)[])[])[];
+ type UAParserProps = 'browser' | 'cpu' | 'device' | 'engine' | 'os';
+ type UAParserExt = Partial> | Partial>[];
+ type UAParserHeaders = Record | IncomingHttpHeaders | Headers;
+
+ export function UAParser(uastring?: string, extensions?: UAParserExt, headers?: UAParserHeaders): IResult;
+ export function UAParser(uastring?: string, headers?: UAParserHeaders): IResult;
+ export function UAParser(extensions?: UAParserExt, headers?: UAParserHeaders): IResult;
+ export function UAParser(headers?: UAParserHeaders): IResult;
+
+ export class UAParser {
+
+ static readonly BROWSER: {
+ NAME: 'name';
+ VERSION: 'version';
+ MAJOR: 'major';
+ TYPE: 'type';
+ };
+ static readonly CPU: {
+ ARCHITECTURE: 'architecture';
+ };
+ static readonly DEVICE: {
+ TYPE: 'type';
+ VENDOR: 'vendor';
+ MODEL: 'model';
+ CONSOLE: 'console';
+ MOBILE: 'mobile';
+ SMARTTV: 'smarttv';
+ TABLET: 'tablet';
+ WEARABLE: 'wearable';
+ XR: 'xr';
+ EMBEDDED: 'embedded';
+ };
+ static readonly ENGINE: {
+ NAME: 'name';
+ VERSION: 'version';
+ };
+ static readonly OS: {
+ NAME: 'name';
+ VERSION: 'version';
+ };
+ static readonly VERSION: string;
+
+ constructor(uastring?: string, extensions?: UAParserExt, headers?: Record);
+ constructor(uastring?: string, headers?: Record);
+ constructor(extensions?: UAParserExt, headers?: Record);
+ constructor(headers?: Record);
+
+ getUA(): string;
+ getBrowser(): IBrowser;
+ getCPU(): ICPU;
+ getDevice(): IDevice;
+ getEngine(): IEngine;
+ getOS(): IOS;
+ getResult(): IResult;
+ setUA(uastring: string): UAParser;
+ }
+}
+
+export as namespace UAParser;
+export = UAParser;
\ No newline at end of file
diff --git a/src/main/ua-parser.js b/src/main/ua-parser.js
new file mode 100755
index 000000000..5f2d042b8
--- /dev/null
+++ b/src/main/ua-parser.js
@@ -0,0 +1,1505 @@
+/////////////////////////////////////////////////////////////////////////////////
+/* UAParser.js v2.0.4
+ Copyright Š 2012-2025 Faisal Salman
+ AGPLv3 License *//*
+ Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
+ Supports browser & node.js environment.
+ Demo : https://uaparser.dev
+ Source : https://github.com/faisalman/ua-parser-js */
+/////////////////////////////////////////////////////////////////////////////////
+
+/* jshint esversion: 3 */
+/* globals window */
+
+(function (window, undefined) {
+
+ 'use strict';
+
+ //////////////
+ // Constants
+ /////////////
+
+ var LIBVERSION = '2.0.4',
+ UA_MAX_LENGTH = 500,
+ USER_AGENT = 'user-agent',
+ EMPTY = '',
+ UNKNOWN = '?',
+
+ // typeof
+ FUNC_TYPE = 'function',
+ UNDEF_TYPE = 'undefined',
+ OBJ_TYPE = 'object',
+ STR_TYPE = 'string',
+
+ // properties
+ UA_BROWSER = 'browser',
+ UA_CPU = 'cpu',
+ UA_DEVICE = 'device',
+ UA_ENGINE = 'engine',
+ UA_OS = 'os',
+ UA_RESULT = 'result',
+
+ NAME = 'name',
+ TYPE = 'type',
+ VENDOR = 'vendor',
+ VERSION = 'version',
+ ARCHITECTURE= 'architecture',
+ MAJOR = 'major',
+ MODEL = 'model',
+
+ // device types
+ CONSOLE = 'console',
+ MOBILE = 'mobile',
+ TABLET = 'tablet',
+ SMARTTV = 'smarttv',
+ WEARABLE = 'wearable',
+ XR = 'xr',
+ EMBEDDED = 'embedded',
+
+ // browser types
+ INAPP = 'inapp',
+
+ // client hints
+ BRANDS = 'brands',
+ FORMFACTORS = 'formFactors',
+ FULLVERLIST = 'fullVersionList',
+ PLATFORM = 'platform',
+ PLATFORMVER = 'platformVersion',
+ BITNESS = 'bitness',
+ CH_HEADER = 'sec-ch-ua',
+ CH_HEADER_FULL_VER_LIST = CH_HEADER + '-full-version-list',
+ CH_HEADER_ARCH = CH_HEADER + '-arch',
+ 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],
+
+ // device vendors
+ AMAZON = 'Amazon',
+ APPLE = 'Apple',
+ ASUS = 'ASUS',
+ BLACKBERRY = 'BlackBerry',
+ GOOGLE = 'Google',
+ HUAWEI = 'Huawei',
+ LENOVO = 'Lenovo',
+ HONOR = 'Honor',
+ LG = 'LG',
+ MICROSOFT = 'Microsoft',
+ MOTOROLA = 'Motorola',
+ NVIDIA = 'Nvidia',
+ ONEPLUS = 'OnePlus',
+ OPPO = 'OPPO',
+ SAMSUNG = 'Samsung',
+ SHARP = 'Sharp',
+ SONY = 'Sony',
+ XIAOMI = 'Xiaomi',
+ ZEBRA = 'Zebra',
+
+ // browsers
+ CHROME = 'Chrome',
+ CHROMIUM = 'Chromium',
+ CHROMECAST = 'Chromecast',
+ EDGE = 'Edge',
+ FIREFOX = 'Firefox',
+ OPERA = 'Opera',
+ FACEBOOK = 'Facebook',
+ SOGOU = 'Sogou',
+
+ PREFIX_MOBILE = 'Mobile ',
+ SUFFIX_BROWSER = ' Browser',
+
+ // os
+ WINDOWS = 'Windows';
+
+ var isWindow = typeof window !== UNDEF_TYPE,
+ NAVIGATOR = (isWindow && window.navigator) ?
+ window.navigator :
+ undefined,
+ NAVIGATOR_UADATA = (NAVIGATOR && NAVIGATOR.userAgentData) ?
+ NAVIGATOR.userAgentData :
+ undefined;
+
+ ///////////
+ // Helper
+ //////////
+
+ var extend = function (defaultRgx, extensions) {
+ var mergedRgx = {};
+ var extraRgx = extensions;
+ if (!isExtensions(extensions)) {
+ extraRgx = {};
+ for (var i in extensions) {
+ for (var j in extensions[i]) {
+ extraRgx[j] = extensions[i][j].concat(extraRgx[j] ? extraRgx[j] : []);
+ }
+ }
+ }
+ for (var k in defaultRgx) {
+ mergedRgx[k] = extraRgx[k] && extraRgx[k].length % 2 === 0 ? extraRgx[k].concat(defaultRgx[k]) : defaultRgx[k];
+ }
+ return mergedRgx;
+ },
+ enumerize = function (arr) {
+ var enums = {};
+ for (var i=0; i 0) {
+ for (var i in str1) {
+ if (lowerize(str2) == lowerize(str1[i])) return true;
+ }
+ return false;
+ }
+ return isString(str1) ? lowerize(str2) == lowerize(str1) : false;
+ },
+ isExtensions = function (obj, deep) {
+ for (var prop in obj) {
+ return /^(browser|cpu|device|engine|os)$/.test(prop) || (deep ? isExtensions(obj[prop]) : false);
+ }
+ },
+ isString = function (val) {
+ return typeof val === STR_TYPE;
+ },
+ itemListToArray = function (header) {
+ if (!header) return undefined;
+ var arr = [];
+ var tokens = strip(/\\?\"/g, header).split(',');
+ for (var i = 0; i < tokens.length; i++) {
+ if (tokens[i].indexOf(';') > -1) {
+ var token = trim(tokens[i]).split(';v=');
+ arr[i] = { brand : token[0], version : token[1] };
+ } else {
+ arr[i] = trim(tokens[i]);
+ }
+ }
+ return arr;
+ },
+ lowerize = function (str) {
+ return isString(str) ? str.toLowerCase() : str;
+ },
+ majorize = function (version) {
+ return isString(version) ? strip(/[^\d\.]/g, version).split('.')[0] : undefined;
+ },
+ setProps = function (arr) {
+ for (var i in arr) {
+ if (!arr.hasOwnProperty(i)) continue;
+
+ var propName = arr[i];
+ if (typeof propName == OBJ_TYPE && propName.length == 2) {
+ this[propName[0]] = propName[1];
+ } else {
+ this[propName] = undefined;
+ }
+ }
+ return this;
+ },
+ strip = function (pattern, str) {
+ return isString(str) ? str.replace(pattern, EMPTY) : str;
+ },
+ stripQuotes = function (str) {
+ 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);
+ }
+ };
+
+ ///////////////
+ // Map helper
+ //////////////
+
+ var rgxMapper = function (ua, arrays) {
+
+ if(!ua || !arrays) return;
+
+ var i = 0, j, k, p, q, matches, match;
+
+ // loop through all regexes maps
+ while (i < arrays.length && !matches) {
+
+ var regex = arrays[i], // even sequence (0,2,4,..)
+ props = arrays[i + 1]; // odd sequence (1,3,5,..)
+ j = k = 0;
+
+ // try matching uastring with regexes
+ while (j < regex.length && !matches) {
+
+ if (!regex[j]) { break; }
+ matches = regex[j++].exec(ua);
+
+ if (!!matches) {
+ for (p = 0; p < props.length; p++) {
+ match = matches[++k];
+ q = props[p];
+ // check if given property is actually array
+ if (typeof q === OBJ_TYPE && q.length > 0) {
+ if (q.length === 2) {
+ if (typeof q[1] == FUNC_TYPE) {
+ // assign modified match
+ this[q[0]] = q[1].call(this, match);
+ } else {
+ // assign given value, ignore regex match
+ this[q[0]] = q[1];
+ }
+ } 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 (q.length > 3) {
+ this[q[0]] = match ? q[1].apply(this, q.slice(2)) : undefined;
+ } else {
+ // call function (usually string mapper)
+ this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
+ }
+ } else {
+ if (q.length == 3) {
+ // sanitize match using given regex
+ this[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
+ } else if (q.length == 4) {
+ this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
+ } else if (q.length > 4) {
+ this[q[0]] = match ? q[3].apply(this, [match.replace(q[1], q[2])].concat(q.slice(4))) : undefined;
+ }
+ }
+ }
+ } else {
+ this[q] = match ? match : undefined;
+ }
+ }
+ }
+ }
+ i += 2;
+ }
+ },
+
+ strMapper = function (str, map) {
+
+ for (var i in map) {
+ // check if current value is array
+ if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
+ for (var j = 0; j < map[i].length; j++) {
+ if (has(map[i][j], str)) {
+ return (i === UNKNOWN) ? undefined : i;
+ }
+ }
+ } else if (has(map[i], str)) {
+ return (i === UNKNOWN) ? undefined : i;
+ }
+ }
+ return map.hasOwnProperty('*') ? map['*'] : str;
+ };
+
+ ///////////////
+ // String map
+ //////////////
+
+ var windowsVersionMap = {
+ 'ME' : '4.90',
+ 'NT 3.51': '3.51',
+ 'NT 4.0': '4.0',
+ '2000' : ['5.0', '5.01'],
+ 'XP' : ['5.1', '5.2'],
+ 'Vista' : '6.0',
+ '7' : '6.1',
+ '8' : '6.2',
+ '8.1' : '6.3',
+ '10' : ['6.4', '10.0'],
+ 'NT' : ''
+ },
+
+ formFactorsMap = {
+ 'embedded' : 'Automotive',
+ 'mobile' : 'Mobile',
+ 'tablet' : ['Tablet', 'EInk'],
+ 'smarttv' : 'TV',
+ 'wearable' : 'Watch',
+ 'xr' : ['VR', 'XR'],
+ '?' : ['Desktop', 'Unknown'],
+ '*' : undefined
+ },
+
+ browserHintsMap = {
+ 'Chrome' : 'Google Chrome',
+ 'Edge' : 'Microsoft Edge',
+ 'Edge WebView2' : 'Microsoft Edge WebView2',
+ 'Chrome WebView': 'Android WebView',
+ 'Chrome Headless':'HeadlessChrome',
+ 'Huawei Browser': 'HuaweiBrowser',
+ 'MIUI Browser' : 'Miui Browser',
+ 'Opera Mobi' : 'OperaMobile',
+ 'Yandex' : 'YaBrowser'
+ };
+
+ //////////////
+ // Regex map
+ /////////////
+
+ var defaultRegexes = {
+
+ browser : [[
+
+ // Most common regardless engine
+ /\b(?:crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
+ ], [VERSION, [NAME, PREFIX_MOBILE + 'Chrome']], [
+ /webview.+edge\/([\w\.]+)/i // Microsoft Edge
+ ], [VERSION, [NAME, EDGE+' WebView']], [
+ /edg(?:e|ios|a)?\/([\w\.]+)/i
+ ], [VERSION, [NAME, 'Edge']], [
+
+ // Presto based
+ /(opera mini)\/([-\w\.]+)/i, // Opera Mini
+ /(opera [mobiletab]{3,6})\b.+version\/([-\w\.]+)/i, // Opera Mobi/Tablet
+ /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i // Opera
+ ], [NAME, VERSION], [
+ /opios[\/ ]+([\w\.]+)/i // Opera mini on iphone >= 8.0
+ ], [VERSION, [NAME, OPERA+' Mini']], [
+ /\bop(?:rg)?x\/([\w\.]+)/i // Opera GX
+ ], [VERSION, [NAME, OPERA+' GX']], [
+ /\bopr\/([\w\.]+)/i // Opera Webkit
+ ], [VERSION, [NAME, OPERA]], [
+
+ // Mixed
+ /\bb[ai]*d(?:uhd|[ub]*[aekoprswx]{5,6})[\/ ]?([\w\.]+)/i // Baidu
+ ], [VERSION, [NAME, 'Baidu']], [
+ /\b(?:mxbrowser|mxios|myie2)\/?([-\w\.]*)\b/i // Maxthon
+ ], [VERSION, [NAME, 'Maxthon']], [
+ /(kindle)\/([\w\.]+)/i, // Kindle
+ /(lunascape|maxthon|netfront|jasmine|blazer|sleipnir)[\/ ]?([\w\.]*)/i,
+ // Lunascape/Maxthon/Netfront/Jasmine/Blazer/Sleipnir
+ // Trident based
+ /(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
+ /(ecosia|weibo)(?:__| \w+@)([\d\.]+)/i // Ecosia/Weibo
+ ], [NAME, VERSION], [
+ /quark(?:pc)?\/([-\w\.]+)/i // Quark
+ ], [VERSION, [NAME, 'Quark']], [
+ /\bddg\/([\w\.]+)/i // DuckDuckGo
+ ], [VERSION, [NAME, 'DuckDuckGo']], [
+ /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i // UCBrowser
+ ], [VERSION, [NAME, 'UCBrowser']], [
+ /microm.+\bqbcore\/([\w\.]+)/i, // WeChat Desktop for Windows Built-in Browser
+ /\bqbcore\/([\w\.]+).+microm/i,
+ /micromessenger\/([\w\.]+)/i // WeChat
+ ], [VERSION, [NAME, 'WeChat']], [
+ /konqueror\/([\w\.]+)/i // Konqueror
+ ], [VERSION, [NAME, 'Konqueror']], [
+ /trident.+rv[: ]([\w\.]{1,9})\b.+like gecko/i // IE11
+ ], [VERSION, [NAME, 'IE']], [
+ /ya(?:search)?browser\/([\w\.]+)/i // Yandex
+ ], [VERSION, [NAME, 'Yandex']], [
+ /slbrowser\/([\w\.]+)/i // Smart Lenovo Browser
+ ], [VERSION, [NAME, 'Smart ' + LENOVO + SUFFIX_BROWSER]], [
+ /(avast|avg)\/([\w\.]+)/i // Avast/AVG Secure Browser
+ ], [[NAME, /(.+)/, '$1 Secure' + SUFFIX_BROWSER], VERSION], [
+ /\bfocus\/([\w\.]+)/i // Firefox Focus
+ ], [VERSION, [NAME, FIREFOX+' Focus']], [
+ /\bopt\/([\w\.]+)/i // Opera Touch
+ ], [VERSION, [NAME, OPERA+' Touch']], [
+ /coc_coc\w+\/([\w\.]+)/i // Coc Coc Browser
+ ], [VERSION, [NAME, 'Coc Coc']], [
+ /dolfin\/([\w\.]+)/i // Dolphin
+ ], [VERSION, [NAME, 'Dolphin']], [
+ /coast\/([\w\.]+)/i // Opera Coast
+ ], [VERSION, [NAME, OPERA+' Coast']], [
+ /miuibrowser\/([\w\.]+)/i // MIUI Browser
+ ], [VERSION, [NAME, 'MIUI' + SUFFIX_BROWSER]], [
+ /fxios\/([\w\.-]+)/i // Firefox for iOS
+ ], [VERSION, [NAME, PREFIX_MOBILE + FIREFOX]], [
+ /\bqihoobrowser\/?([\w\.]*)/i // 360
+ ], [VERSION, [NAME, '360']], [
+ /\b(qq)\/([\w\.]+)/i // QQ
+ ], [[NAME, /(.+)/, '$1Browser'], VERSION], [
+ /(oculus|sailfish|huawei|vivo|pico)browser\/([\w\.]+)/i
+ ], [[NAME, /(.+)/, '$1' + SUFFIX_BROWSER], VERSION], [ // Oculus/Sailfish/HuaweiBrowser/VivoBrowser/PicoBrowser
+ /samsungbrowser\/([\w\.]+)/i // Samsung Internet
+ ], [VERSION, [NAME, SAMSUNG + ' Internet']], [
+ /metasr[\/ ]?([\d\.]+)/i // Sogou Explorer
+ ], [VERSION, [NAME, SOGOU + ' Explorer']], [
+ /(sogou)mo\w+\/([\d\.]+)/i // Sogou Mobile
+ ], [[NAME, SOGOU + ' Mobile'], VERSION], [
+ /(electron)\/([\w\.]+) safari/i, // Electron-based App
+ /(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
+ ], [NAME], [
+ /ome\/([\w\.]+) \w* ?(iron) saf/i, // Iron
+ /ome\/([\w\.]+).+qihu (360)[es]e/i // 360
+ ], [VERSION, NAME], [
+
+ // WebView
+ /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i // Facebook App for iOS & Android
+ ], [[NAME, FACEBOOK], VERSION, [TYPE, INAPP]], [
+ /(kakao(?:talk|story))[\/ ]([\w\.]+)/i, // Kakao App
+ /(naver)\(.*?(\d+\.[\w\.]+).*\)/i, // Naver InApp
+ /(daum)apps[\/ ]([\w\.]+)/i, // Daum App
+ /safari (line)\/([\w\.]+)/i, // Line App for iOS
+ /\b(line)\/([\w\.]+)\/iab/i, // Line App for Android
+ /(alipay)client\/([\w\.]+)/i, // Alipay
+ /(twitter)(?:and| f.+e\/([\w\.]+))/i, // Twitter
+ /(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
+ ], [VERSION, [NAME, 'TikTok'], [TYPE, INAPP]], [
+ /\[(linkedin)app\]/i // LinkedIn App for iOS & Android
+ ], [NAME, [TYPE, INAPP]], [
+ /(zalo(?:app)?)[\/\sa-z]*([\w\.-]+)/i // Zalo
+ ], [[NAME, /(.+)/, 'Zalo'], VERSION, [TYPE, INAPP]], [
+
+ /(chromium)[\/ ]([-\w\.]+)/i // Chromium
+ ], [NAME, VERSION], [
+
+ /headlesschrome(?:\/([\w\.]+)| )/i // Chrome Headless
+ ], [VERSION, [NAME, CHROME+' Headless']], [
+
+ /wv\).+chrome\/([\w\.]+).+edgw\//i // Edge WebView2
+ ], [VERSION, [NAME, EDGE+' WebView2']], [
+
+ / wv\).+(chrome)\/([\w\.]+)/i // Chrome WebView
+ ], [[NAME, CHROME+' WebView'], VERSION], [
+
+ /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i // Android Browser
+ ], [VERSION, [NAME, 'Android' + SUFFIX_BROWSER]], [
+
+ /chrome\/([\w\.]+) mobile/i // Chrome Mobile
+ ], [VERSION, [NAME, PREFIX_MOBILE + 'Chrome']], [
+
+ /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i // Chrome/OmniWeb/Arora/Tizen/Nokia
+ ], [NAME, VERSION], [
+
+ /version\/([\w\.\,]+) .*mobile(?:\/\w+ | ?)safari/i // Safari Mobile
+ ], [VERSION, [NAME, PREFIX_MOBILE + 'Safari']], [
+ /iphone .*mobile(?:\/\w+ | ?)safari/i
+ ], [[NAME, PREFIX_MOBILE + 'Safari']], [
+ /version\/([\w\.\,]+) .*(safari)/i // Safari
+ ], [VERSION, NAME], [
+ /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
+ ], [NAME, [VERSION, '1']], [
+
+ /(webkit|khtml)\/([\w\.]+)/i
+ ], [NAME, VERSION], [
+
+ // Gecko based
+ /(?:mobile|tablet);.*(firefox)\/([\w\.-]+)/i // Firefox Mobile
+ ], [[NAME, PREFIX_MOBILE + FIREFOX], VERSION], [
+ /(navigator|netscape\d?)\/([-\w\.]+)/i // Netscape
+ ], [[NAME, 'Netscape'], VERSION], [
+ /(wolvic|librewolf)\/([\w\.]+)/i // Wolvic/LibreWolf
+ ], [NAME, VERSION], [
+ /mobile vr; rv:([\w\.]+)\).+firefox/i // Firefox Reality
+ ], [VERSION, [NAME, FIREFOX+' Reality']], [
+ /ekiohf.+(flow)\/([\w\.]+)/i, // Flow
+ /(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,
+ // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
+ /(firefox)\/([\w\.]+)/i, // Other Firefox-based
+ /(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i, // Mozilla
+
+ // Other
+ /(amaya|dillo|doris|icab|ladybird|lynx|mosaic|netsurf|obigo|polaris|w3m|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
+ // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Obigo/Mosaic/Go/ICE/UP.Browser/Ladybird
+ /\b(links) \(([\w\.]+)/i // Links
+ ], [NAME, [VERSION, /_/g, '.']], [
+
+ /(cobalt)\/([\w\.]+)/i // Cobalt
+ ], [NAME, [VERSION, /[^\d\.]+./, EMPTY]]
+ ],
+
+ cpu : [[
+
+ /\b((amd|x|x86[-_]?|wow|win)64)\b/i // AMD64 (x64)
+ ], [[ARCHITECTURE, 'amd64']], [
+
+ /(ia32(?=;))/i, // IA32 (quicktime)
+ /\b((i[346]|x)86)(pc)?\b/i // IA32 (x86)
+ ], [[ARCHITECTURE, 'ia32']], [
+
+ /\b(aarch64|arm(v?[89]e?l?|_?64))\b/i // ARM64
+ ], [[ARCHITECTURE, 'arm64']], [
+
+ /\b(arm(v[67])?ht?n?[fl]p?)\b/i // ARMHF
+ ], [[ARCHITECTURE, 'armhf']], [
+
+ // PocketPC mistakenly identified as PowerPC
+ /( (ce|mobile); ppc;|\/[\w\.]+arm\b)/i
+ ], [[ARCHITECTURE, 'arm']], [
+
+ / sun4\w[;\)]/i // SPARC
+ ], [[ARCHITECTURE, 'sparc']], [
+ // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
+ /\b(avr32|ia64(?=;)|68k(?=\))|\barm(?=v([1-7]|[5-7]1)l?|;|eabi)|(irix|mips|sparc)(64)?\b|pa-risc)/i,
+ /((ppc|powerpc)(64)?)( mac|;|\))/i, // PowerPC
+ /(?:osf1|[freopnt]{3,4}bsd) (alpha)/i // Alpha
+ ], [[ARCHITECTURE, /ower/, EMPTY, lowerize]], [
+ /winnt.+\[axp/i
+ ], [[ARCHITECTURE, 'alpha']]
+ ],
+
+ device : [[
+
+ //////////////////////////
+ // MOBILES & TABLETS
+ /////////////////////////
+
+ // Samsung
+ /\b(sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i
+ ], [MODEL, [VENDOR, SAMSUNG], [TYPE, TABLET]], [
+ /\b((?:s[cgp]h|gt|sm)-(?![lr])\w+|sc[g-]?[\d]+a?|galaxy nexus)/i,
+ /samsung[- ]((?!sm-[lr]|browser)[-\w]+)/i,
+ /sec-(sgh\w+)/i
+ ], [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]], [
+
+ // Apple
+ /(?:\/|\()(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
+ ], [MODEL, [VENDOR, APPLE], [TYPE, TABLET]], [
+ /(macintosh);/i
+ ], [MODEL, [VENDOR, APPLE]], [
+
+ // Sharp
+ /\b(sh-?[altvz]?\d\d[a-ekm]?)/i
+ ], [MODEL, [VENDOR, SHARP], [TYPE, MOBILE]], [
+
+ // Honor
+ /\b((?:brt|eln|hey2?|gdi|jdn)-a?[lnw]09|(?:ag[rm]3?|jdn2|kob2)-a?[lw]0[09]hn)(?: bui|\)|;)/i
+ ], [MODEL, [VENDOR, HONOR], [TYPE, TABLET]], [
+ /honor([-\w ]+)[;\)]/i
+ ], [MODEL, [VENDOR, HONOR], [TYPE, MOBILE]], [
+
+ // 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
+ ], [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
+ ],[[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
+ / ([\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,
+ /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i
+ ], [MODEL, [VENDOR, ONEPLUS], [TYPE, MOBILE]], [
+
+ // OPPO
+ /; (\w+) bui.+ oppo/i,
+ /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i
+ ], [MODEL, [VENDOR, OPPO], [TYPE, MOBILE]], [
+ /\b(opd2(\d{3}a?))(?: bui|\))/i
+ ], [MODEL, [VENDOR, strMapper, { 'OnePlus' : ['203', '304', '403', '404', '413', '415'], '*' : OPPO }], [TYPE, TABLET]], [
+
+ // BLU
+ /(vivo (5r?|6|8l?|go|one|s|x[il]?[2-4]?)[\w\+ ]*)(?: bui|\))/i // Vivo series
+ ], [MODEL, [VENDOR, 'BLU'], [TYPE, MOBILE]], [
+
+ // Vivo
+ /; vivo (\w+)(?: bui|\))/i,
+ /\b(v[12]\d{3}\w?[at])(?: bui|;)/i
+ ], [MODEL, [VENDOR, 'Vivo'], [TYPE, MOBILE]], [
+
+ // Realme
+ /\b(rmx[1-3]\d{3})(?: bui|;|\))/i
+ ], [MODEL, [VENDOR, 'Realme'], [TYPE, MOBILE]], [
+
+ // Lenovo
+ /(ideatab[-\w ]+|602lv|d-42a|a101lv|a2109a|a3500-hv|s[56]000|pb-6505[my]|tb-?x?\d{3,4}(?:f[cu]|xu|[av])|yt\d?-[jx]?\d+[lfmx])( bui|;|\)|\/)/i,
+ /lenovo ?(b[68]0[08]0-?[hf]?|tab(?:[\w- ]+?)|tb[\w-]{6,7})( bui|;|\)|\/)/i
+ ], [MODEL, [VENDOR, LENOVO], [TYPE, TABLET]], [
+ /lenovo[-_ ]?([-\w ]+?)(?: bui|\)|\/)/i
+ ], [MODEL, [VENDOR, LENOVO], [TYPE, MOBILE]], [
+
+ // Motorola
+ /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i,
+ /\bmot(?:orola)?[- ]([\w\s]+)(\)| bui)/i,
+ /((?:moto(?! 360)[-\w\(\) ]+|xt\d{3,4}[cgkosw\+]?[-\d]*|nexus 6)(?= bui|\)))/i
+ ], [MODEL, [VENDOR, MOTOROLA], [TYPE, MOBILE]], [
+ /\b(mz60\d|xoom[2 ]{0,2}) build\//i
+ ], [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]], [
+
+ // LG
+ /((?=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,
+ /\blg-?([\d\w]+) bui/i
+ ], [MODEL, [VENDOR, LG], [TYPE, MOBILE]], [
+
+ // Nokia
+ /(nokia) (t[12][01])/i
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+ /(?:maemo|nokia).*(n900|lumia \d+|rm-\d+)/i,
+ /nokia[-_ ]?(([-\w\. ]*))/i
+ ], [[MODEL, /_/g, ' '], [TYPE, MOBILE], [VENDOR, 'Nokia']], [
+
+ // Google
+ /(pixel (c|tablet))\b/i // Google Pixel C/Tablet
+ ], [MODEL, [VENDOR, GOOGLE], [TYPE, TABLET]], [
+ // Google Pixel
+ /droid.+;(?: google)? (g(01[13]a|020[aem]|025[jn]|1b60|1f8f|2ybb|4s1m|576d|5nz6|8hhn|8vou|a02099|c15s|d1yq|e2ae|ec77|gh2x|kv4x|p4bc|pj41|r83y|tt9q|ur25|wvk6)|pixel[\d ]*a?( pro)?( xl)?( fold)?( \(5g\))?)( bui|\))/i
+ ], [MODEL, [VENDOR, GOOGLE], [TYPE, MOBILE]], [
+ /(google) (pixelbook( go)?)/i
+ ], [VENDOR, MODEL], [
+
+ // Sony
+ /droid.+; (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-\w\w\d\d)(?= bui|\).+chrome\/(?![1-6]{0,1}\d\.))/i
+ ], [MODEL, [VENDOR, SONY], [TYPE, MOBILE]], [
+ /sony tablet [ps]/i,
+ /\b(?:sony)?sgp\w+(?: bui|\))/i
+ ], [[MODEL, 'Xperia Tablet'], [VENDOR, SONY], [TYPE, TABLET]], [
+
+ // Amazon
+ /(alexa)webm/i,
+ /(kf[a-z]{2}wi|aeo(?!bc)\w\w)( bui|\))/i, // Kindle Fire without Silk / Echo Show
+ /(kf[a-z]+)( bui|\)).+silk\//i // Kindle Fire HD
+ ], [MODEL, [VENDOR, AMAZON], [TYPE, TABLET]], [
+ /((?:sd|kf)[0349hijorstuw]+)( bui|\)).+silk\//i // Fire Phone
+ ], [[MODEL, /(.+)/g, 'Fire Phone $1'], [VENDOR, AMAZON], [TYPE, MOBILE]], [
+
+ // BlackBerry
+ /(playbook);[-\w\),; ]+(rim)/i // BlackBerry PlayBook
+ ], [MODEL, VENDOR, [TYPE, TABLET]], [
+ /\b((?:bb[a-f]|st[hv])100-\d)/i,
+ /\(bb10; (\w+)/i // BlackBerry 10
+ ], [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]], [
+
+ // Asus
+ /(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i
+ ], [MODEL, [VENDOR, ASUS], [TYPE, TABLET]], [
+ / (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i
+ ], [MODEL, [VENDOR, ASUS], [TYPE, MOBILE]], [
+
+ // HTC
+ /(nexus 9)/i // HTC Nexus 9
+ ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [
+ /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i, // HTC
+
+ // ZTE
+ /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i,
+ /(alcatel|geeksphone|nexian|panasonic(?!(?:;|\.))|sony(?!-bra))[-_ ]?([-\w]*)/i // Alcatel/GeeksPhone/Nexian/Panasonic/Sony
+ ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [
+
+ // TCL
+ /tcl (xess p17aa)/i,
+ /droid [\w\.]+; ((?:8[14]9[16]|9(?:0(?:48|60|8[01])|1(?:3[27]|66)|2(?:6[69]|9[56])|466))[gqswx])(_\w(\w|\w\w))?(\)| bui)/i
+ ], [MODEL, [VENDOR, 'TCL'], [TYPE, TABLET]], [
+ /droid [\w\.]+; (418(?:7d|8v)|5087z|5102l|61(?:02[dh]|25[adfh]|27[ai]|56[dh]|59k|65[ah])|a509dl|t(?:43(?:0w|1[adepqu])|50(?:6d|7[adju])|6(?:09dl|10k|12b|71[efho]|76[hjk])|7(?:66[ahju]|67[hw]|7[045][bh]|71[hk]|73o|76[ho]|79w|81[hks]?|82h|90[bhsy]|99b)|810[hs]))(_\w(\w|\w\w))?(\)| bui)/i
+ ], [MODEL, [VENDOR, 'TCL'], [TYPE, MOBILE]], [
+
+ // itel
+ /(itel) ((\w+))/i
+ ], [[VENDOR, lowerize], MODEL, [TYPE, strMapper, { 'tablet' : ['p10001l', 'w7001'], '*' : 'mobile' }]], [
+
+ // Acer
+ /droid.+; ([ab][1-7]-?[0178a]\d\d?)/i
+ ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [
+
+ // Meizu
+ /droid.+; (m[1-5] note) bui/i,
+ /\bmz-([-\w]{2,})/i
+ ], [MODEL, [VENDOR, 'Meizu'], [TYPE, MOBILE]], [
+
+ // Ulefone
+ /; ((?:power )?armor(?:[\w ]{0,8}))(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Ulefone'], [TYPE, MOBILE]], [
+
+ // Energizer
+ /; (energy ?\w+)(?: bui|\))/i,
+ /; energizer ([\w ]+)(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Energizer'], [TYPE, MOBILE]], [
+
+ // Cat
+ /; cat (b35);/i,
+ /; (b15q?|s22 flip|s48c|s62 pro)(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Cat'], [TYPE, MOBILE]], [
+
+ // Smartfren
+ /((?:new )?andromax[\w- ]+)(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Smartfren'], [TYPE, MOBILE]], [
+
+ // Nothing
+ /droid.+; (a(in)?(0(15|59|6[35])|142)p?)/i
+ ], [MODEL, [VENDOR, 'Nothing'], [TYPE, MOBILE]], [
+
+ // Archos
+ /; (x67 5g|tikeasy \w+|ac[1789]\d\w+)( b|\))/i,
+ /archos ?(5|gamepad2?|([\w ]*[t1789]|hello) ?\d+[\w ]*)( b|\))/i
+ ], [MODEL, [VENDOR, 'Archos'], [TYPE, TABLET]], [
+ /archos ([\w ]+)( b|\))/i,
+ /; (ac[3-6]\d\w{2,8})( b|\))/i
+ ], [MODEL, [VENDOR, 'Archos'], [TYPE, MOBILE]], [
+
+ // HMD
+ /; (n159v)/i
+ ], [MODEL, [VENDOR, 'HMD'], [TYPE, MOBILE]], [
+
+ // MIXED
+ /(imo) (tab \w+)/i, // IMO
+ /(infinix|tecno) (x1101b?|p904|dp(7c|8d|10a)( pro)?|p70[1-3]a?|p904|t1101)/i // Infinix XPad / Tecno
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+ /(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
+ /(hp) ([\w ]+\w)/i, // HP iPAQ
+ /(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia
+ /(oppo) ?([\w ]+) bui/i, // OPPO
+ /droid[^;]+; (philips)[_ ]([sv-x][\d]{3,4}[xz]?)/i // Philips
+ ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+
+ /(kobo)\s(ereader|touch)/i, // Kobo
+ /(hp).+(touchpad(?!.+tablet)|tablet)/i, // HP TouchPad
+ /(kindle)\/([\w\.]+)/i // Kindle
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+ /(surface duo)/i // Surface Duo
+ ], [MODEL, [VENDOR, MICROSOFT], [TYPE, TABLET]], [
+ /droid [\d\.]+; (fp\du?)(?: b|\))/i // Fairphone
+ ], [MODEL, [VENDOR, 'Fairphone'], [TYPE, MOBILE]], [
+ /((?:tegranote|shield t(?!.+d tv))[\w- ]*?)(?: b|\))/i // Nvidia Tablets
+ ], [MODEL, [VENDOR, NVIDIA], [TYPE, TABLET]], [
+ /(sprint) (\w+)/i // Sprint Phones
+ ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+ /(kin\.[onetw]{3})/i // Microsoft Kin
+ ], [[MODEL, /\./g, ' '], [VENDOR, MICROSOFT], [TYPE, MOBILE]], [
+ /droid.+; ([c6]+|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i // Zebra
+ ], [MODEL, [VENDOR, ZEBRA], [TYPE, TABLET]], [
+ /droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i
+ ], [MODEL, [VENDOR, ZEBRA], [TYPE, MOBILE]], [
+
+ ///////////////////
+ // SMARTTVS
+ ///////////////////
+
+ /(philips)[\w ]+tv/i, // Philips
+ /smart-tv.+(samsung)/i // Samsung
+ ], [VENDOR, [TYPE, SMARTTV]], [
+ /hbbtv.+maple;(\d+)/i
+ ], [[MODEL, /^/, 'SmartTV'], [VENDOR, SAMSUNG], [TYPE, SMARTTV]], [
+ /(vizio)(?: |.+model\/)(\w+-\w+)/i, // Vizio
+ /tcast.+(lg)e?. ([-\w]+)/i // LG SmartTV
+ ], [VENDOR, MODEL, [TYPE, SMARTTV]], [
+ /(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i
+ ], [[VENDOR, LG], [TYPE, SMARTTV]], [
+ /(apple) ?tv/i // Apple TV
+ ], [VENDOR, [MODEL, APPLE+' TV'], [TYPE, SMARTTV]], [
+ /crkey.*devicetype\/chromecast/i // Google Chromecast Third Generation
+ ], [[MODEL, CHROMECAST+' Third Generation'], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /crkey.*devicetype\/([^/]*)/i // Google Chromecast with specific device type
+ ], [[MODEL, /^/, 'Chromecast '], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /fuchsia.*crkey/i // Google Chromecast Nest Hub
+ ], [[MODEL, CHROMECAST+' Nest Hub'], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /crkey/i // Google Chromecast, Linux-based or unknown
+ ], [[MODEL, CHROMECAST], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /(portaltv)/i // Facebook Portal TV
+ ], [MODEL, [VENDOR, FACEBOOK], [TYPE, SMARTTV]], [
+ /droid.+aft(\w+)( bui|\))/i // Fire TV
+ ], [MODEL, [VENDOR, AMAZON], [TYPE, SMARTTV]], [
+ /(shield \w+ tv)/i // Nvidia Shield TV
+ ], [MODEL, [VENDOR, NVIDIA], [TYPE, SMARTTV]], [
+ /\(dtv[\);].+(aquos)/i,
+ /(aquos-tv[\w ]+)\)/i // Sharp
+ ], [MODEL, [VENDOR, SHARP], [TYPE, SMARTTV]],[
+ /(bravia[\w ]+)( bui|\))/i // Sony
+ ], [MODEL, [VENDOR, SONY], [TYPE, SMARTTV]], [
+ /(mi(tv|box)-?\w+) bui/i // Xiaomi
+ ], [MODEL, [VENDOR, XIAOMI], [TYPE, SMARTTV]], [
+ /Hbbtv.*(technisat) (.*);/i // TechniSAT
+ ], [VENDOR, MODEL, [TYPE, SMARTTV]], [
+ /\b(roku)[\dx]*[\)\/]((?:dvp-)?[\d\.]*)/i, // Roku
+ /hbbtv\/\d+\.\d+\.\d+ +\([\w\+ ]*; *([\w\d][^;]*);([^;]*)/i // HbbTV devices
+ ], [[VENDOR, /.+\/(\w+)/, '$1', strMapper, {'LG':'lge'}], [MODEL, trim], [TYPE, SMARTTV]], [
+
+ ///////////////////
+ // CONSOLES
+ ///////////////////
+
+ /(playstation \w+)/i // Playstation
+ ], [MODEL, [VENDOR, SONY], [TYPE, CONSOLE]], [
+ /\b(xbox(?: one)?(?!; xbox))[\); ]/i // Microsoft Xbox
+ ], [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]], [
+
+ ///////////////////
+ // WEARABLES
+ ///////////////////
+
+ /\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
+ ], [VENDOR, MODEL, [TYPE, WEARABLE]], [
+ /(ow(?:19|20)?we?[1-3]{1,3})/i // Oppo Watch
+ ], [MODEL, [VENDOR, OPPO], [TYPE, WEARABLE]], [
+ /(watch)(?: ?os[,\/]|\d,\d\/)[\d\.]+/i // Apple Watch
+ ], [MODEL, [VENDOR, APPLE], [TYPE, WEARABLE]], [
+ /(opwwe\d{3})/i // OnePlus Watch
+ ], [MODEL, [VENDOR, ONEPLUS], [TYPE, WEARABLE]], [
+ /(moto 360)/i // Motorola 360
+ ], [MODEL, [VENDOR, MOTOROLA], [TYPE, WEARABLE]], [
+ /(smartwatch 3)/i // Sony SmartWatch
+ ], [MODEL, [VENDOR, SONY], [TYPE, WEARABLE]], [
+ /(g watch r)/i // LG G Watch R
+ ], [MODEL, [VENDOR, LG], [TYPE, WEARABLE]], [
+ /droid.+; (wt63?0{2,3})\)/i
+ ], [MODEL, [VENDOR, ZEBRA], [TYPE, WEARABLE]], [
+
+ ///////////////////
+ // XR
+ ///////////////////
+
+ /droid.+; (glass) \d/i // Google Glass
+ ], [MODEL, [VENDOR, GOOGLE], [TYPE, XR]], [
+ /(pico) ([\w ]+) os\d/i // Pico
+ ], [VENDOR, MODEL, [TYPE, XR]], [
+ /(quest( \d| pro)?s?).+vr/i // Meta Quest
+ ], [MODEL, [VENDOR, FACEBOOK], [TYPE, XR]], [
+ /mobile vr; rv.+firefox/i // Unidentifiable VR device using Firefox Reality / Wolvic
+ ], [[TYPE, XR]], [
+
+ ///////////////////
+ // EMBEDDED
+ ///////////////////
+
+ /(tesla)(?: qtcarbrowser|\/[-\w\.]+)/i // Tesla
+ ], [VENDOR, [TYPE, EMBEDDED]], [
+ /(aeobc)\b/i // Echo Dot
+ ], [MODEL, [VENDOR, AMAZON], [TYPE, EMBEDDED]], [
+ /(homepod).+mac os/i // Apple HomePod
+ ], [MODEL, [VENDOR, APPLE], [TYPE, EMBEDDED]], [
+ /windows iot/i // Unidentifiable embedded device using Windows IoT
+ ], [[TYPE, EMBEDDED]], [
+
+ ////////////////////
+ // MIXED (GENERIC)
+ ///////////////////
+
+ /droid.+; ([\w- ]+) (4k|android|smart|google)[- ]?tv/i // Unidentifiable SmartTV
+ ], [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
+ ], [MODEL, [TYPE, strMapper, { 'mobile' : 'Mobile', 'xr' : 'VR', '*' : TABLET }]], [
+ /\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i // Unidentifiable Tablet
+ ], [[TYPE, TABLET]], [
+ /(phone|mobile(?:[;\/]| [ \w\/\.]*safari)|pda(?=.+windows ce))/i // Unidentifiable Mobile
+ ], [[TYPE, MOBILE]], [
+ /droid .+?; ([\w\. -]+)( bui|\))/i // Generic Android Device
+ ], [MODEL, [VENDOR, 'Generic']]
+ ],
+
+ engine : [[
+
+ /windows.+ edge\/([\w\.]+)/i // EdgeHTML
+ ], [VERSION, [NAME, EDGE+'HTML']], [
+
+ /(arkweb)\/([\w\.]+)/i // ArkWeb
+ ], [NAME, VERSION], [
+
+ /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i // Blink
+ ], [VERSION, [NAME, 'Blink']], [
+
+ /(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
+ /(icab)[\/ ]([23]\.[\d\.]+)/i, // iCab
+
+ /\b(libweb)/i // LibWeb
+ ], [NAME, VERSION], [
+ /ladybird\//i
+ ], [[NAME, 'LibWeb']], [
+
+ /rv\:([\w\.]{1,9})\b.+(gecko)/i // Gecko
+ ], [VERSION, NAME]
+ ],
+
+ os : [[
+
+ // Windows
+ /(windows nt) (6\.[23]); arm/i // Windows RT
+ ], [[NAME, /N/, 'R'], [VERSION, strMapper, windowsVersionMap]], [
+ /(windows (?:phone|mobile|iot))(?: os)?[\/ ]?([\d\.]*( se)?)/i, // Windows IoT/Mobile/Phone
+ // Windows NT/3.1/95/98/ME/2000/XP/Vista/7/8/8.1/10/11
+ /(windows)[\/ ](1[01]|2000|3\.1|7|8(\.1)?|9[58]|me|server 20\d\d( r2)?|vista|xp)/i
+ ], [NAME, VERSION], [
+ /windows nt ?([\d\.\)]*)(?!.+xbox)/i,
+ /\bwin(?=3| ?9|n)(?:nt| 9x )?([\d\.;]*)/i
+ ], [[VERSION, /(;|\))/g, '', strMapper, windowsVersionMap], [NAME, WINDOWS]], [
+ /(windows ce)\/?([\d\.]*)/i // Windows CE
+ ], [NAME, VERSION], [
+
+ // iOS/macOS
+ /[adehimnop]{4,7}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
+ /(?:ios;fbsv\/|iphone.+ios[\/ ])([\d\.]+)/i,
+ /cfnetwork\/.+darwin/i
+ ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [
+ /(mac os x) ?([\w\. ]*)/i,
+ /(macintosh|mac_powerpc\b)(?!.+(haiku|morphos))/i // Mac OS
+ ], [[NAME, 'macOS'], [VERSION, /_/g, '.']], [
+
+ // Google Chromecast
+ /android ([\d\.]+).*crkey/i // Google Chromecast, Android-based
+ ], [VERSION, [NAME, CHROMECAST + ' Android']], [
+ /fuchsia.*crkey\/([\d\.]+)/i // Google Chromecast, Fuchsia-based
+ ], [VERSION, [NAME, CHROMECAST + ' Fuchsia']], [
+ /crkey\/([\d\.]+).*devicetype\/smartspeaker/i // Google Chromecast, Linux-based Smart Speaker
+ ], [VERSION, [NAME, CHROMECAST + ' SmartSpeaker']], [
+ /linux.*crkey\/([\d\.]+)/i // Google Chromecast, Legacy Linux-based
+ ], [VERSION, [NAME, CHROMECAST + ' Linux']], [
+ /crkey\/([\d\.]+)/i // Google Chromecast, unknown
+ ], [VERSION, [NAME, CHROMECAST]], [
+
+ // Mobile OSes
+ /droid ([\w\.]+)\b.+(android[- ]x86)/i // Android-x86
+ ], [VERSION, NAME], [
+ /(ubuntu) ([\w\.]+) like android/i // Ubuntu Touch
+ ], [[NAME, /(.+)/, '$1 Touch'], VERSION], [
+ /(harmonyos)[\/ ]?([\d\.]*)/i, // HarmonyOS
+ // Android/Blackberry/WebOS/QNX/Bada/RIM/KaiOS/Maemo/MeeGo/S40/Sailfish OS/OpenHarmony/Tizen
+ /(android|bada|blackberry|kaios|maemo|meego|openharmony|qnx|rim tablet os|sailfish|series40|symbian|tizen)\w*[-\/\.; ]?([\d\.]*)/i
+ ], [NAME, VERSION], [
+ /\(bb(10);/i // BlackBerry 10
+ ], [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
+ ], [VERSION, [NAME, FIREFOX+' OS']], [
+ /\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i, // WebOS
+ /webos(?:[ \/]?|\.tv-20(?=2[2-9]))(\d[\d\.]*)/i
+ ], [VERSION, [NAME, 'webOS']], [
+ /web0s;.+?(?:chr[o0]me|safari)\/(\d+)/i
+ // https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine
+ ], [[VERSION, strMapper, {'25':'120','24':'108','23':'94','22':'87','6':'79','5':'68','4':'53','3':'38','2':'538','1':'537','*':'TV'}], [NAME, 'webOS']], [
+ /watch(?: ?os[,\/]|\d,\d\/)([\d\.]+)/i // watchOS
+ ], [VERSION, [NAME, 'watchOS']], [
+
+ // Google ChromeOS
+ /(cros) [\w]+(?:\)| ([\w\.]+)\b)/i // Chromium OS
+ ], [[NAME, "Chrome OS"], VERSION],[
+
+ // Smart TVs
+ /panasonic;(viera)/i, // Panasonic Viera
+ /(netrange)mmh/i, // Netrange
+ /(nettv)\/(\d+\.[\w\.]+)/i, // NetTV
+
+ // Console
+ /(nintendo|playstation) (\w+)/i, // Nintendo/Playstation
+ /(xbox); +xbox ([^\);]+)/i, // Microsoft Xbox (360, One, X, S, Series X, Series S)
+ /(pico) .+os([\w\.]+)/i, // Pico
+
+ // Other
+ /\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i, // Joli/Palm
+ /linux.+(mint)[\/\(\) ]?([\w\.]*)/i, // Mint
+ /(mageia|vectorlinux|fuchsia|arcaos|arch(?= ?linux))[;l ]([\d\.]*)/i, // Mageia/VectorLinux/Fuchsia/ArcaOS/Arch
+ /([kxln]?ubuntu|debian|suse|opensuse|gentoo|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire|knoppix)(?: gnu[\/ ]linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i,
+ // Ubuntu/Debian/SUSE/Gentoo/Slackware/Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus/Raspbian/Plan9/Minix/RISCOS/Contiki/Deepin/Manjaro/elementary/Sabayon/Linspire/Knoppix
+ /((?:open)?solaris)[-\/ ]?([\w\.]*)/i, // Solaris
+ /\b(aix)[; ]([1-9\.]{0,4})/i, // AIX
+ /(hurd|linux|morphos)(?: (?:arm|x86|ppc)\w*| ?)([\w\.]*)/i, // Hurd/Linux/MorphOS
+ /(gnu) ?([\w\.]*)/i, // GNU
+ /\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i, // FreeBSD/NetBSD/OpenBSD/PC-BSD/GhostBSD/DragonFly
+ /(haiku) ?(r\d)?/i // Haiku
+ ], [NAME, VERSION], [
+ /(sunos) ?([\d\.]*)/i // Solaris
+ ], [[NAME, 'Solaris'], VERSION], [
+ /\b(beos|os\/2|amigaos|openvms|hp-ux|serenityos)/i, // BeOS/OS2/AmigaOS/OpenVMS/HP-UX/SerenityOS
+ /(unix) ?([\w\.]*)/i // UNIX
+ ], [NAME, VERSION]
+ ]
+ };
+
+ /////////////////
+ // Factories
+ ////////////////
+
+ 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]]
+ ]);
+ setProps.call(props.isIgnore, [
+ [UA_BROWSER, [VERSION, MAJOR]],
+ [UA_ENGINE, [VERSION]],
+ [UA_OS, [VERSION]]
+ ]);
+ setProps.call(props.isIgnoreRgx, [
+ [UA_BROWSER, / ?browser$/i],
+ [UA_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]]
+ ]);
+ return props;
+ })();
+
+ var createIData = function (item, itemType) {
+
+ var init_props = defaultProps.init[itemType],
+ is_ignoreProps = defaultProps.isIgnore[itemType] || 0,
+ is_ignoreRgx = defaultProps.isIgnoreRgx[itemType] || 0,
+ toString_props = defaultProps.toString[itemType] || 0;
+
+ function IData () {
+ setProps.call(this, init_props);
+ }
+
+ IData.prototype.getItem = function () {
+ return item;
+ };
+
+ IData.prototype.withClientHints = function () {
+
+ // nodejs / non-client-hints browsers
+ if (!NAVIGATOR_UADATA) {
+ return item
+ .parseCH()
+ .get();
+ }
+
+ // browsers based on chromium 85+
+ return NAVIGATOR_UADATA
+ .getHighEntropyValues(CH_ALL_VALUES)
+ .then(function (res) {
+ return item
+ .setCH(new UACHData(res, false))
+ .parseCH()
+ .get();
+ });
+ };
+
+ IData.prototype.withFeatureCheck = function () {
+ return item.detectFeature().get();
+ };
+
+ if (itemType != UA_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) {
+ is = !is;
+ break;
+ }
+ }
+ return is;
+ };
+ IData.prototype.toString = function () {
+ var str = EMPTY;
+ for (var i in toString_props) {
+ if (typeof(this[toString_props[i]]) !== UNDEF_TYPE) {
+ str += (str ? ' ' : EMPTY) + this[toString_props[i]];
+ }
+ }
+ return str || UNDEF_TYPE;
+ };
+ }
+
+ 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];
+ }
+ }
+ };
+ IDataResolve.prototype = {
+ is : IData.prototype.is,
+ toString : IData.prototype.toString
+ };
+ var resolveData = new IDataResolve();
+ cb(resolveData);
+ return resolveData;
+ };
+ }
+
+ return new IData();
+ };
+
+ /////////////////
+ // Constructor
+ ////////////////
+
+ function UACHData (uach, isHttpUACH) {
+ uach = uach || {};
+ 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])]
+ ]);
+ } else {
+ for (var prop in uach) {
+ if(this.hasOwnProperty(prop) && typeof uach[prop] !== UNDEF_TYPE) 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= 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],
+ ['uaCH', uaCH],
+ ['rgxMap', rgxMap],
+ ['data', createIData(this, itemType)]
+ ]);
+
+ return this;
+ }
+
+ function UAParser (ua, extensions, headers) {
+
+ if (typeof ua === OBJ_TYPE) {
+ if (isExtensions(ua, true)) {
+ if (typeof extensions === OBJ_TYPE) {
+ headers = extensions; // case UAParser(extensions, headers)
+ }
+ extensions = ua; // case UAParser(extensions)
+ } else {
+ headers = ua; // case UAParser(headers)
+ extensions = undefined;
+ }
+ ua = undefined;
+ } else if (typeof ua === STR_TYPE && !isExtensions(extensions, true)) {
+ headers = extensions; // case UAParser(ua, headers)
+ extensions = undefined;
+ }
+
+ // Convert Headers object into a plain object
+ if (headers && typeof headers.append === FUNC_TYPE) {
+ var kv = {};
+ headers.forEach(function (v, k) { kv[k] = v; });
+ headers = kv;
+ }
+
+ if (!(this instanceof UAParser)) {
+ return new UAParser(ua, extensions, headers).getResult();
+ }
+
+ var userAgent = typeof ua === STR_TYPE ? 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
+
+ httpUACH = new UACHData(headers, true),
+ regexMap = extensions ?
+ extend(defaultRegexes, extensions) :
+ defaultRegexes,
+
+ createItemFunc = function (itemType) {
+ if (itemType == UA_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())
+ .get();
+ };
+ } else {
+ return function () {
+ return new UAItem(itemType, userAgent, regexMap[itemType], httpUACH)
+ .parseUA()
+ .get();
+ };
+ }
+ };
+
+ // 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)],
+ ['getUA', function () { return userAgent; }],
+ ['setUA', function (ua) {
+ if (isString(ua))
+ userAgent = ua.length > UA_MAX_LENGTH ? trim(ua, UA_MAX_LENGTH) : ua;
+ return this;
+ }]
+ ])
+ .setUA(userAgent);
+
+ return this;
+ }
+
+ UAParser.VERSION = LIBVERSION;
+ UAParser.BROWSER = enumerize([NAME, VERSION, MAJOR, TYPE]);
+ UAParser.CPU = enumerize([ARCHITECTURE]);
+ UAParser.DEVICE = enumerize([MODEL, VENDOR, TYPE, CONSOLE, MOBILE, SMARTTV, TABLET, WEARABLE, EMBEDDED]);
+ UAParser.ENGINE = UAParser.OS = enumerize([NAME, VERSION]);
+
+ ///////////
+ // Export
+ //////////
+
+ // check js environment
+ if (typeof exports !== UNDEF_TYPE) {
+ // nodejs env
+ if (typeof module !== UNDEF_TYPE && module.exports) {
+ exports = module.exports = UAParser;
+ }
+ exports.UAParser = UAParser;
+ } else {
+ // requirejs env (optional)
+ if (typeof define === FUNC_TYPE && define.amd) {
+ define(function () {
+ return UAParser;
+ });
+ } else if (isWindow) {
+ // browser env
+ window.UAParser = UAParser;
+ }
+ }
+
+ // jQuery/Zepto specific (optional)
+ // Note:
+ // In AMD env the global scope should be kept clean, but jQuery is an exception.
+ // jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
+ // and we should catch that.
+ var $ = isWindow && (window.jQuery || window.Zepto);
+ if ($ && !$.ua) {
+ var parser = new UAParser();
+ $.ua = parser.getResult();
+ $.ua.get = function () {
+ return parser.getUA();
+ };
+ $.ua.set = function (ua) {
+ parser.setUA(ua);
+ var result = parser.getResult();
+ for (var prop in result) {
+ $.ua[prop] = result[prop];
+ }
+ };
+ }
+
+})(typeof window === 'object' ? window : this);
diff --git a/src/main/ua-parser.mjs b/src/main/ua-parser.mjs
new file mode 100644
index 000000000..2b45cda66
--- /dev/null
+++ b/src/main/ua-parser.mjs
@@ -0,0 +1,1460 @@
+// Generated ESM version of ua-parser-js
+// DO NOT EDIT THIS FILE!
+// Source: /src/main/ua-parser.js
+
+/////////////////////////////////////////////////////////////////////////////////
+/* UAParser.js v2.0.4
+ Copyright Š 2012-2025 Faisal Salman
+ AGPLv3 License *//*
+ Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
+ Supports browser & node.js environment.
+ Demo : https://uaparser.dev
+ Source : https://github.com/faisalman/ua-parser-js */
+/////////////////////////////////////////////////////////////////////////////////
+
+/* jshint esversion: 6 */
+/* globals window */
+
+
+
+ //////////////
+ // Constants
+ /////////////
+
+ var LIBVERSION = '2.0.4',
+ UA_MAX_LENGTH = 500,
+ USER_AGENT = 'user-agent',
+ EMPTY = '',
+ UNKNOWN = '?',
+
+ // typeof
+ FUNC_TYPE = 'function',
+ UNDEF_TYPE = 'undefined',
+ OBJ_TYPE = 'object',
+ STR_TYPE = 'string',
+
+ // properties
+ UA_BROWSER = 'browser',
+ UA_CPU = 'cpu',
+ UA_DEVICE = 'device',
+ UA_ENGINE = 'engine',
+ UA_OS = 'os',
+ UA_RESULT = 'result',
+
+ NAME = 'name',
+ TYPE = 'type',
+ VENDOR = 'vendor',
+ VERSION = 'version',
+ ARCHITECTURE= 'architecture',
+ MAJOR = 'major',
+ MODEL = 'model',
+
+ // device types
+ CONSOLE = 'console',
+ MOBILE = 'mobile',
+ TABLET = 'tablet',
+ SMARTTV = 'smarttv',
+ WEARABLE = 'wearable',
+ XR = 'xr',
+ EMBEDDED = 'embedded',
+
+ // browser types
+ INAPP = 'inapp',
+
+ // client hints
+ BRANDS = 'brands',
+ FORMFACTORS = 'formFactors',
+ FULLVERLIST = 'fullVersionList',
+ PLATFORM = 'platform',
+ PLATFORMVER = 'platformVersion',
+ BITNESS = 'bitness',
+ CH_HEADER = 'sec-ch-ua',
+ CH_HEADER_FULL_VER_LIST = CH_HEADER + '-full-version-list',
+ CH_HEADER_ARCH = CH_HEADER + '-arch',
+ 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],
+
+ // device vendors
+ AMAZON = 'Amazon',
+ APPLE = 'Apple',
+ ASUS = 'ASUS',
+ BLACKBERRY = 'BlackBerry',
+ GOOGLE = 'Google',
+ HUAWEI = 'Huawei',
+ LENOVO = 'Lenovo',
+ HONOR = 'Honor',
+ LG = 'LG',
+ MICROSOFT = 'Microsoft',
+ MOTOROLA = 'Motorola',
+ NVIDIA = 'Nvidia',
+ ONEPLUS = 'OnePlus',
+ OPPO = 'OPPO',
+ SAMSUNG = 'Samsung',
+ SHARP = 'Sharp',
+ SONY = 'Sony',
+ XIAOMI = 'Xiaomi',
+ ZEBRA = 'Zebra',
+
+ // browsers
+ CHROME = 'Chrome',
+ CHROMIUM = 'Chromium',
+ CHROMECAST = 'Chromecast',
+ EDGE = 'Edge',
+ FIREFOX = 'Firefox',
+ OPERA = 'Opera',
+ FACEBOOK = 'Facebook',
+ SOGOU = 'Sogou',
+
+ PREFIX_MOBILE = 'Mobile ',
+ SUFFIX_BROWSER = ' Browser',
+
+ // os
+ WINDOWS = 'Windows';
+
+ var isWindow = typeof window !== UNDEF_TYPE,
+ NAVIGATOR = (isWindow && window.navigator) ?
+ window.navigator :
+ undefined,
+ NAVIGATOR_UADATA = (NAVIGATOR && NAVIGATOR.userAgentData) ?
+ NAVIGATOR.userAgentData :
+ undefined;
+
+ ///////////
+ // Helper
+ //////////
+
+ var extend = function (defaultRgx, extensions) {
+ var mergedRgx = {};
+ var extraRgx = extensions;
+ if (!isExtensions(extensions)) {
+ extraRgx = {};
+ for (var i in extensions) {
+ for (var j in extensions[i]) {
+ extraRgx[j] = extensions[i][j].concat(extraRgx[j] ? extraRgx[j] : []);
+ }
+ }
+ }
+ for (var k in defaultRgx) {
+ mergedRgx[k] = extraRgx[k] && extraRgx[k].length % 2 === 0 ? extraRgx[k].concat(defaultRgx[k]) : defaultRgx[k];
+ }
+ return mergedRgx;
+ },
+ enumerize = function (arr) {
+ var enums = {};
+ for (var i=0; i 0) {
+ for (var i in str1) {
+ if (lowerize(str2) == lowerize(str1[i])) return true;
+ }
+ return false;
+ }
+ return isString(str1) ? lowerize(str2) == lowerize(str1) : false;
+ },
+ isExtensions = function (obj, deep) {
+ for (var prop in obj) {
+ return /^(browser|cpu|device|engine|os)$/.test(prop) || (deep ? isExtensions(obj[prop]) : false);
+ }
+ },
+ isString = function (val) {
+ return typeof val === STR_TYPE;
+ },
+ itemListToArray = function (header) {
+ if (!header) return undefined;
+ var arr = [];
+ var tokens = strip(/\\?\"/g, header).split(',');
+ for (var i = 0; i < tokens.length; i++) {
+ if (tokens[i].indexOf(';') > -1) {
+ var token = trim(tokens[i]).split(';v=');
+ arr[i] = { brand : token[0], version : token[1] };
+ } else {
+ arr[i] = trim(tokens[i]);
+ }
+ }
+ return arr;
+ },
+ lowerize = function (str) {
+ return isString(str) ? str.toLowerCase() : str;
+ },
+ majorize = function (version) {
+ return isString(version) ? strip(/[^\d\.]/g, version).split('.')[0] : undefined;
+ },
+ setProps = function (arr) {
+ for (var i in arr) {
+ if (!arr.hasOwnProperty(i)) continue;
+
+ var propName = arr[i];
+ if (typeof propName == OBJ_TYPE && propName.length == 2) {
+ this[propName[0]] = propName[1];
+ } else {
+ this[propName] = undefined;
+ }
+ }
+ return this;
+ },
+ strip = function (pattern, str) {
+ return isString(str) ? str.replace(pattern, EMPTY) : str;
+ },
+ stripQuotes = function (str) {
+ 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);
+ }
+ };
+
+ ///////////////
+ // Map helper
+ //////////////
+
+ var rgxMapper = function (ua, arrays) {
+
+ if(!ua || !arrays) return;
+
+ var i = 0, j, k, p, q, matches, match;
+
+ // loop through all regexes maps
+ while (i < arrays.length && !matches) {
+
+ var regex = arrays[i], // even sequence (0,2,4,..)
+ props = arrays[i + 1]; // odd sequence (1,3,5,..)
+ j = k = 0;
+
+ // try matching uastring with regexes
+ while (j < regex.length && !matches) {
+
+ if (!regex[j]) { break; }
+ matches = regex[j++].exec(ua);
+
+ if (!!matches) {
+ for (p = 0; p < props.length; p++) {
+ match = matches[++k];
+ q = props[p];
+ // check if given property is actually array
+ if (typeof q === OBJ_TYPE && q.length > 0) {
+ if (q.length === 2) {
+ if (typeof q[1] == FUNC_TYPE) {
+ // assign modified match
+ this[q[0]] = q[1].call(this, match);
+ } else {
+ // assign given value, ignore regex match
+ this[q[0]] = q[1];
+ }
+ } 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 (q.length > 3) {
+ this[q[0]] = match ? q[1].apply(this, q.slice(2)) : undefined;
+ } else {
+ // call function (usually string mapper)
+ this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
+ }
+ } else {
+ if (q.length == 3) {
+ // sanitize match using given regex
+ this[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
+ } else if (q.length == 4) {
+ this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
+ } else if (q.length > 4) {
+ this[q[0]] = match ? q[3].apply(this, [match.replace(q[1], q[2])].concat(q.slice(4))) : undefined;
+ }
+ }
+ }
+ } else {
+ this[q] = match ? match : undefined;
+ }
+ }
+ }
+ }
+ i += 2;
+ }
+ },
+
+ strMapper = function (str, map) {
+
+ for (var i in map) {
+ // check if current value is array
+ if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
+ for (var j = 0; j < map[i].length; j++) {
+ if (has(map[i][j], str)) {
+ return (i === UNKNOWN) ? undefined : i;
+ }
+ }
+ } else if (has(map[i], str)) {
+ return (i === UNKNOWN) ? undefined : i;
+ }
+ }
+ return map.hasOwnProperty('*') ? map['*'] : str;
+ };
+
+ ///////////////
+ // String map
+ //////////////
+
+ var windowsVersionMap = {
+ 'ME' : '4.90',
+ 'NT 3.51': '3.51',
+ 'NT 4.0': '4.0',
+ '2000' : ['5.0', '5.01'],
+ 'XP' : ['5.1', '5.2'],
+ 'Vista' : '6.0',
+ '7' : '6.1',
+ '8' : '6.2',
+ '8.1' : '6.3',
+ '10' : ['6.4', '10.0'],
+ 'NT' : ''
+ },
+
+ formFactorsMap = {
+ 'embedded' : 'Automotive',
+ 'mobile' : 'Mobile',
+ 'tablet' : ['Tablet', 'EInk'],
+ 'smarttv' : 'TV',
+ 'wearable' : 'Watch',
+ 'xr' : ['VR', 'XR'],
+ '?' : ['Desktop', 'Unknown'],
+ '*' : undefined
+ },
+
+ browserHintsMap = {
+ 'Chrome' : 'Google Chrome',
+ 'Edge' : 'Microsoft Edge',
+ 'Edge WebView2' : 'Microsoft Edge WebView2',
+ 'Chrome WebView': 'Android WebView',
+ 'Chrome Headless':'HeadlessChrome',
+ 'Huawei Browser': 'HuaweiBrowser',
+ 'MIUI Browser' : 'Miui Browser',
+ 'Opera Mobi' : 'OperaMobile',
+ 'Yandex' : 'YaBrowser'
+ };
+
+ //////////////
+ // Regex map
+ /////////////
+
+ var defaultRegexes = {
+
+ browser : [[
+
+ // Most common regardless engine
+ /\b(?:crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
+ ], [VERSION, [NAME, PREFIX_MOBILE + 'Chrome']], [
+ /webview.+edge\/([\w\.]+)/i // Microsoft Edge
+ ], [VERSION, [NAME, EDGE+' WebView']], [
+ /edg(?:e|ios|a)?\/([\w\.]+)/i
+ ], [VERSION, [NAME, 'Edge']], [
+
+ // Presto based
+ /(opera mini)\/([-\w\.]+)/i, // Opera Mini
+ /(opera [mobiletab]{3,6})\b.+version\/([-\w\.]+)/i, // Opera Mobi/Tablet
+ /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i // Opera
+ ], [NAME, VERSION], [
+ /opios[\/ ]+([\w\.]+)/i // Opera mini on iphone >= 8.0
+ ], [VERSION, [NAME, OPERA+' Mini']], [
+ /\bop(?:rg)?x\/([\w\.]+)/i // Opera GX
+ ], [VERSION, [NAME, OPERA+' GX']], [
+ /\bopr\/([\w\.]+)/i // Opera Webkit
+ ], [VERSION, [NAME, OPERA]], [
+
+ // Mixed
+ /\bb[ai]*d(?:uhd|[ub]*[aekoprswx]{5,6})[\/ ]?([\w\.]+)/i // Baidu
+ ], [VERSION, [NAME, 'Baidu']], [
+ /\b(?:mxbrowser|mxios|myie2)\/?([-\w\.]*)\b/i // Maxthon
+ ], [VERSION, [NAME, 'Maxthon']], [
+ /(kindle)\/([\w\.]+)/i, // Kindle
+ /(lunascape|maxthon|netfront|jasmine|blazer|sleipnir)[\/ ]?([\w\.]*)/i,
+ // Lunascape/Maxthon/Netfront/Jasmine/Blazer/Sleipnir
+ // Trident based
+ /(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
+ /(ecosia|weibo)(?:__| \w+@)([\d\.]+)/i // Ecosia/Weibo
+ ], [NAME, VERSION], [
+ /quark(?:pc)?\/([-\w\.]+)/i // Quark
+ ], [VERSION, [NAME, 'Quark']], [
+ /\bddg\/([\w\.]+)/i // DuckDuckGo
+ ], [VERSION, [NAME, 'DuckDuckGo']], [
+ /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i // UCBrowser
+ ], [VERSION, [NAME, 'UCBrowser']], [
+ /microm.+\bqbcore\/([\w\.]+)/i, // WeChat Desktop for Windows Built-in Browser
+ /\bqbcore\/([\w\.]+).+microm/i,
+ /micromessenger\/([\w\.]+)/i // WeChat
+ ], [VERSION, [NAME, 'WeChat']], [
+ /konqueror\/([\w\.]+)/i // Konqueror
+ ], [VERSION, [NAME, 'Konqueror']], [
+ /trident.+rv[: ]([\w\.]{1,9})\b.+like gecko/i // IE11
+ ], [VERSION, [NAME, 'IE']], [
+ /ya(?:search)?browser\/([\w\.]+)/i // Yandex
+ ], [VERSION, [NAME, 'Yandex']], [
+ /slbrowser\/([\w\.]+)/i // Smart Lenovo Browser
+ ], [VERSION, [NAME, 'Smart ' + LENOVO + SUFFIX_BROWSER]], [
+ /(avast|avg)\/([\w\.]+)/i // Avast/AVG Secure Browser
+ ], [[NAME, /(.+)/, '$1 Secure' + SUFFIX_BROWSER], VERSION], [
+ /\bfocus\/([\w\.]+)/i // Firefox Focus
+ ], [VERSION, [NAME, FIREFOX+' Focus']], [
+ /\bopt\/([\w\.]+)/i // Opera Touch
+ ], [VERSION, [NAME, OPERA+' Touch']], [
+ /coc_coc\w+\/([\w\.]+)/i // Coc Coc Browser
+ ], [VERSION, [NAME, 'Coc Coc']], [
+ /dolfin\/([\w\.]+)/i // Dolphin
+ ], [VERSION, [NAME, 'Dolphin']], [
+ /coast\/([\w\.]+)/i // Opera Coast
+ ], [VERSION, [NAME, OPERA+' Coast']], [
+ /miuibrowser\/([\w\.]+)/i // MIUI Browser
+ ], [VERSION, [NAME, 'MIUI' + SUFFIX_BROWSER]], [
+ /fxios\/([\w\.-]+)/i // Firefox for iOS
+ ], [VERSION, [NAME, PREFIX_MOBILE + FIREFOX]], [
+ /\bqihoobrowser\/?([\w\.]*)/i // 360
+ ], [VERSION, [NAME, '360']], [
+ /\b(qq)\/([\w\.]+)/i // QQ
+ ], [[NAME, /(.+)/, '$1Browser'], VERSION], [
+ /(oculus|sailfish|huawei|vivo|pico)browser\/([\w\.]+)/i
+ ], [[NAME, /(.+)/, '$1' + SUFFIX_BROWSER], VERSION], [ // Oculus/Sailfish/HuaweiBrowser/VivoBrowser/PicoBrowser
+ /samsungbrowser\/([\w\.]+)/i // Samsung Internet
+ ], [VERSION, [NAME, SAMSUNG + ' Internet']], [
+ /metasr[\/ ]?([\d\.]+)/i // Sogou Explorer
+ ], [VERSION, [NAME, SOGOU + ' Explorer']], [
+ /(sogou)mo\w+\/([\d\.]+)/i // Sogou Mobile
+ ], [[NAME, SOGOU + ' Mobile'], VERSION], [
+ /(electron)\/([\w\.]+) safari/i, // Electron-based App
+ /(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
+ ], [NAME], [
+ /ome\/([\w\.]+) \w* ?(iron) saf/i, // Iron
+ /ome\/([\w\.]+).+qihu (360)[es]e/i // 360
+ ], [VERSION, NAME], [
+
+ // WebView
+ /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i // Facebook App for iOS & Android
+ ], [[NAME, FACEBOOK], VERSION, [TYPE, INAPP]], [
+ /(kakao(?:talk|story))[\/ ]([\w\.]+)/i, // Kakao App
+ /(naver)\(.*?(\d+\.[\w\.]+).*\)/i, // Naver InApp
+ /(daum)apps[\/ ]([\w\.]+)/i, // Daum App
+ /safari (line)\/([\w\.]+)/i, // Line App for iOS
+ /\b(line)\/([\w\.]+)\/iab/i, // Line App for Android
+ /(alipay)client\/([\w\.]+)/i, // Alipay
+ /(twitter)(?:and| f.+e\/([\w\.]+))/i, // Twitter
+ /(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
+ ], [VERSION, [NAME, 'TikTok'], [TYPE, INAPP]], [
+ /\[(linkedin)app\]/i // LinkedIn App for iOS & Android
+ ], [NAME, [TYPE, INAPP]], [
+
+ /(chromium)[\/ ]([-\w\.]+)/i // Chromium
+ ], [NAME, VERSION], [
+
+ /headlesschrome(?:\/([\w\.]+)| )/i // Chrome Headless
+ ], [VERSION, [NAME, CHROME+' Headless']], [
+
+ /wv\).+chrome\/([\w\.]+).+edgw\//i // Edge WebView2
+ ], [VERSION, [NAME, EDGE+' WebView2']], [
+
+ / wv\).+(chrome)\/([\w\.]+)/i // Chrome WebView
+ ], [[NAME, CHROME+' WebView'], VERSION], [
+
+ /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i // Android Browser
+ ], [VERSION, [NAME, 'Android' + SUFFIX_BROWSER]], [
+
+ /chrome\/([\w\.]+) mobile/i // Chrome Mobile
+ ], [VERSION, [NAME, PREFIX_MOBILE + 'Chrome']], [
+
+ /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i // Chrome/OmniWeb/Arora/Tizen/Nokia
+ ], [NAME, VERSION], [
+
+ /version\/([\w\.\,]+) .*mobile(?:\/\w+ | ?)safari/i // Safari Mobile
+ ], [VERSION, [NAME, PREFIX_MOBILE + 'Safari']], [
+ /iphone .*mobile(?:\/\w+ | ?)safari/i
+ ], [[NAME, PREFIX_MOBILE + 'Safari']], [
+ /version\/([\w\.\,]+) .*(safari)/i // Safari
+ ], [VERSION, NAME], [
+ /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
+ ], [NAME, [VERSION, '1']], [
+
+ /(webkit|khtml)\/([\w\.]+)/i
+ ], [NAME, VERSION], [
+
+ // Gecko based
+ /(?:mobile|tablet);.*(firefox)\/([\w\.-]+)/i // Firefox Mobile
+ ], [[NAME, PREFIX_MOBILE + FIREFOX], VERSION], [
+ /(navigator|netscape\d?)\/([-\w\.]+)/i // Netscape
+ ], [[NAME, 'Netscape'], VERSION], [
+ /(wolvic|librewolf)\/([\w\.]+)/i // Wolvic/LibreWolf
+ ], [NAME, VERSION], [
+ /mobile vr; rv:([\w\.]+)\).+firefox/i // Firefox Reality
+ ], [VERSION, [NAME, FIREFOX+' Reality']], [
+ /ekiohf.+(flow)\/([\w\.]+)/i, // Flow
+ /(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,
+ // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
+ /(firefox)\/([\w\.]+)/i, // Other Firefox-based
+ /(mozilla)\/([\w\.]+) .+rv\:.+gecko\/\d+/i, // Mozilla
+
+ // Other
+ /(amaya|dillo|doris|icab|ladybird|lynx|mosaic|netsurf|obigo|polaris|w3m|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
+ // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Obigo/Mosaic/Go/ICE/UP.Browser/Ladybird
+ /\b(links) \(([\w\.]+)/i // Links
+ ], [NAME, [VERSION, /_/g, '.']], [
+
+ /(cobalt)\/([\w\.]+)/i // Cobalt
+ ], [NAME, [VERSION, /[^\d\.]+./, EMPTY]]
+ ],
+
+ cpu : [[
+
+ /\b((amd|x|x86[-_]?|wow|win)64)\b/i // AMD64 (x64)
+ ], [[ARCHITECTURE, 'amd64']], [
+
+ /(ia32(?=;))/i, // IA32 (quicktime)
+ /\b((i[346]|x)86)(pc)?\b/i // IA32 (x86)
+ ], [[ARCHITECTURE, 'ia32']], [
+
+ /\b(aarch64|arm(v?[89]e?l?|_?64))\b/i // ARM64
+ ], [[ARCHITECTURE, 'arm64']], [
+
+ /\b(arm(v[67])?ht?n?[fl]p?)\b/i // ARMHF
+ ], [[ARCHITECTURE, 'armhf']], [
+
+ // PocketPC mistakenly identified as PowerPC
+ /( (ce|mobile); ppc;|\/[\w\.]+arm\b)/i
+ ], [[ARCHITECTURE, 'arm']], [
+
+ /((ppc|powerpc)(64)?)( mac|;|\))/i // PowerPC
+ ], [[ARCHITECTURE, /ower/, EMPTY, lowerize]], [
+
+ / sun4\w[;\)]/i // SPARC
+ ], [[ARCHITECTURE, 'sparc']], [
+
+ /\b(avr32|ia64(?=;)|68k(?=\))|\barm(?=v([1-7]|[5-7]1)l?|;|eabi)|(irix|mips|sparc)(64)?\b|pa-risc)/i
+ // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
+ ], [[ARCHITECTURE, lowerize]]
+ ],
+
+ device : [[
+
+ //////////////////////////
+ // MOBILES & TABLETS
+ /////////////////////////
+
+ // Samsung
+ /\b(sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i
+ ], [MODEL, [VENDOR, SAMSUNG], [TYPE, TABLET]], [
+ /\b((?:s[cgp]h|gt|sm)-(?![lr])\w+|sc[g-]?[\d]+a?|galaxy nexus)/i,
+ /samsung[- ]((?!sm-[lr]|browser)[-\w]+)/i,
+ /sec-(sgh\w+)/i
+ ], [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]], [
+
+ // Apple
+ /(?:\/|\()(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
+ ], [MODEL, [VENDOR, APPLE], [TYPE, TABLET]], [
+ /(macintosh);/i
+ ], [MODEL, [VENDOR, APPLE]], [
+
+ // Sharp
+ /\b(sh-?[altvz]?\d\d[a-ekm]?)/i
+ ], [MODEL, [VENDOR, SHARP], [TYPE, MOBILE]], [
+
+ // Honor
+ /\b((?:brt|eln|hey2?|gdi|jdn)-a?[lnw]09|(?:ag[rm]3?|jdn2|kob2)-a?[lw]0[09]hn)(?: bui|\)|;)/i
+ ], [MODEL, [VENDOR, HONOR], [TYPE, TABLET]], [
+ /honor([-\w ]+)[;\)]/i
+ ], [MODEL, [VENDOR, HONOR], [TYPE, MOBILE]], [
+
+ // 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
+ ], [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
+ ],[[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
+ / ([\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,
+ /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i
+ ], [MODEL, [VENDOR, ONEPLUS], [TYPE, MOBILE]], [
+
+ // OPPO
+ /; (\w+) bui.+ oppo/i,
+ /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i
+ ], [MODEL, [VENDOR, OPPO], [TYPE, MOBILE]], [
+ /\b(opd2(\d{3}a?))(?: bui|\))/i
+ ], [MODEL, [VENDOR, strMapper, { 'OnePlus' : ['203', '304', '403', '404', '413', '415'], '*' : OPPO }], [TYPE, TABLET]], [
+
+ // BLU
+ /(vivo (5r?|6|8l?|go|one|s|x[il]?[2-4]?)[\w\+ ]*)(?: bui|\))/i // Vivo series
+ ], [MODEL, [VENDOR, 'BLU'], [TYPE, MOBILE]], [
+
+ // Vivo
+ /; vivo (\w+)(?: bui|\))/i,
+ /\b(v[12]\d{3}\w?[at])(?: bui|;)/i
+ ], [MODEL, [VENDOR, 'Vivo'], [TYPE, MOBILE]], [
+
+ // Realme
+ /\b(rmx[1-3]\d{3})(?: bui|;|\))/i
+ ], [MODEL, [VENDOR, 'Realme'], [TYPE, MOBILE]], [
+
+ // Lenovo
+ /(ideatab[-\w ]+|602lv|d-42a|a101lv|a2109a|a3500-hv|s[56]000|pb-6505[my]|tb-?x?\d{3,4}(?:f[cu]|xu|[av])|yt\d?-[jx]?\d+[lfmx])( bui|;|\)|\/)/i,
+ /lenovo ?(b[68]0[08]0-?[hf]?|tab(?:[\w- ]+?)|tb[\w-]{6,7})( bui|;|\)|\/)/i
+ ], [MODEL, [VENDOR, LENOVO], [TYPE, TABLET]], [
+ /lenovo[-_ ]?([-\w ]+?)(?: bui|\)|\/)/i
+ ], [MODEL, [VENDOR, LENOVO], [TYPE, MOBILE]], [
+
+ // Motorola
+ /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i,
+ /\bmot(?:orola)?[- ]([\w\s]+)(\)| bui)/i,
+ /((?:moto(?! 360)[-\w\(\) ]+|xt\d{3,4}[cgkosw\+]?[-\d]*|nexus 6)(?= bui|\)))/i
+ ], [MODEL, [VENDOR, MOTOROLA], [TYPE, MOBILE]], [
+ /\b(mz60\d|xoom[2 ]{0,2}) build\//i
+ ], [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]], [
+
+ // LG
+ /((?=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,
+ /\blg-?([\d\w]+) bui/i
+ ], [MODEL, [VENDOR, LG], [TYPE, MOBILE]], [
+
+ // Nokia
+ /(nokia) (t[12][01])/i
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+ /(?:maemo|nokia).*(n900|lumia \d+|rm-\d+)/i,
+ /nokia[-_ ]?(([-\w\. ]*))/i
+ ], [[MODEL, /_/g, ' '], [TYPE, MOBILE], [VENDOR, 'Nokia']], [
+
+ // Google
+ /(pixel (c|tablet))\b/i // Google Pixel C/Tablet
+ ], [MODEL, [VENDOR, GOOGLE], [TYPE, TABLET]], [
+ // Google Pixel
+ /droid.+;(?: google)? (g(01[13]a|020[aem]|025[jn]|1b60|1f8f|2ybb|4s1m|576d|5nz6|8hhn|8vou|a02099|c15s|d1yq|e2ae|ec77|gh2x|kv4x|p4bc|pj41|r83y|tt9q|ur25|wvk6)|pixel[\d ]*a?( pro)?( xl)?( fold)?( \(5g\))?)( bui|\))/i
+ ], [MODEL, [VENDOR, GOOGLE], [TYPE, MOBILE]], [
+ /(google) (pixelbook( go)?)/i
+ ], [VENDOR, MODEL], [
+
+ // Sony
+ /droid.+; (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-\w\w\d\d)(?= bui|\).+chrome\/(?![1-6]{0,1}\d\.))/i
+ ], [MODEL, [VENDOR, SONY], [TYPE, MOBILE]], [
+ /sony tablet [ps]/i,
+ /\b(?:sony)?sgp\w+(?: bui|\))/i
+ ], [[MODEL, 'Xperia Tablet'], [VENDOR, SONY], [TYPE, TABLET]], [
+
+ // Amazon
+ /(alexa)webm/i,
+ /(kf[a-z]{2}wi|aeo(?!bc)\w\w)( bui|\))/i, // Kindle Fire without Silk / Echo Show
+ /(kf[a-z]+)( bui|\)).+silk\//i // Kindle Fire HD
+ ], [MODEL, [VENDOR, AMAZON], [TYPE, TABLET]], [
+ /((?:sd|kf)[0349hijorstuw]+)( bui|\)).+silk\//i // Fire Phone
+ ], [[MODEL, /(.+)/g, 'Fire Phone $1'], [VENDOR, AMAZON], [TYPE, MOBILE]], [
+
+ // BlackBerry
+ /(playbook);[-\w\),; ]+(rim)/i // BlackBerry PlayBook
+ ], [MODEL, VENDOR, [TYPE, TABLET]], [
+ /\b((?:bb[a-f]|st[hv])100-\d)/i,
+ /\(bb10; (\w+)/i // BlackBerry 10
+ ], [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]], [
+
+ // Asus
+ /(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i
+ ], [MODEL, [VENDOR, ASUS], [TYPE, TABLET]], [
+ / (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i
+ ], [MODEL, [VENDOR, ASUS], [TYPE, MOBILE]], [
+
+ // HTC
+ /(nexus 9)/i // HTC Nexus 9
+ ], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [
+ /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i, // HTC
+
+ // ZTE
+ /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i,
+ /(alcatel|geeksphone|nexian|panasonic(?!(?:;|\.))|sony(?!-bra))[-_ ]?([-\w]*)/i // Alcatel/GeeksPhone/Nexian/Panasonic/Sony
+ ], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [
+
+ // TCL
+ /tcl (xess p17aa)/i,
+ /droid [\w\.]+; ((?:8[14]9[16]|9(?:0(?:48|60|8[01])|1(?:3[27]|66)|2(?:6[69]|9[56])|466))[gqswx])(_\w(\w|\w\w))?(\)| bui)/i
+ ], [MODEL, [VENDOR, 'TCL'], [TYPE, TABLET]], [
+ /droid [\w\.]+; (418(?:7d|8v)|5087z|5102l|61(?:02[dh]|25[adfh]|27[ai]|56[dh]|59k|65[ah])|a509dl|t(?:43(?:0w|1[adepqu])|50(?:6d|7[adju])|6(?:09dl|10k|12b|71[efho]|76[hjk])|7(?:66[ahju]|67[hw]|7[045][bh]|71[hk]|73o|76[ho]|79w|81[hks]?|82h|90[bhsy]|99b)|810[hs]))(_\w(\w|\w\w))?(\)| bui)/i
+ ], [MODEL, [VENDOR, 'TCL'], [TYPE, MOBILE]], [
+
+ // itel
+ /(itel) ((\w+))/i
+ ], [[VENDOR, lowerize], MODEL, [TYPE, strMapper, { 'tablet' : ['p10001l', 'w7001'], '*' : 'mobile' }]], [
+
+ // Acer
+ /droid.+; ([ab][1-7]-?[0178a]\d\d?)/i
+ ], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [
+
+ // Meizu
+ /droid.+; (m[1-5] note) bui/i,
+ /\bmz-([-\w]{2,})/i
+ ], [MODEL, [VENDOR, 'Meizu'], [TYPE, MOBILE]], [
+
+ // Ulefone
+ /; ((?:power )?armor(?:[\w ]{0,8}))(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Ulefone'], [TYPE, MOBILE]], [
+
+ // Energizer
+ /; (energy ?\w+)(?: bui|\))/i,
+ /; energizer ([\w ]+)(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Energizer'], [TYPE, MOBILE]], [
+
+ // Cat
+ /; cat (b35);/i,
+ /; (b15q?|s22 flip|s48c|s62 pro)(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Cat'], [TYPE, MOBILE]], [
+
+ // Smartfren
+ /((?:new )?andromax[\w- ]+)(?: bui|\))/i
+ ], [MODEL, [VENDOR, 'Smartfren'], [TYPE, MOBILE]], [
+
+ // Nothing
+ /droid.+; (a(in)?(0(15|59|6[35])|142)p?)/i
+ ], [MODEL, [VENDOR, 'Nothing'], [TYPE, MOBILE]], [
+
+ // Archos
+ /; (x67 5g|tikeasy \w+|ac[1789]\d\w+)( b|\))/i,
+ /archos ?(5|gamepad2?|([\w ]*[t1789]|hello) ?\d+[\w ]*)( b|\))/i
+ ], [MODEL, [VENDOR, 'Archos'], [TYPE, TABLET]], [
+ /archos ([\w ]+)( b|\))/i,
+ /; (ac[3-6]\d\w{2,8})( b|\))/i
+ ], [MODEL, [VENDOR, 'Archos'], [TYPE, MOBILE]], [
+
+ // HMD
+ /; (n159v)/i
+ ], [MODEL, [VENDOR, 'HMD'], [TYPE, MOBILE]], [
+
+ // MIXED
+ /(imo) (tab \w+)/i, // IMO
+ /(infinix|tecno) (x1101b?|p904|dp(7c|8d|10a)( pro)?|p70[1-3]a?|p904|t1101)/i // Infinix XPad / Tecno
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+ /(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
+ /(hp) ([\w ]+\w)/i, // HP iPAQ
+ /(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia
+ /(oppo) ?([\w ]+) bui/i // OPPO
+ ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+
+ /(kobo)\s(ereader|touch)/i, // Kobo
+ /(hp).+(touchpad(?!.+tablet)|tablet)/i, // HP TouchPad
+ /(kindle)\/([\w\.]+)/i // Kindle
+ ], [VENDOR, MODEL, [TYPE, TABLET]], [
+
+ /(surface duo)/i // Surface Duo
+ ], [MODEL, [VENDOR, MICROSOFT], [TYPE, TABLET]], [
+ /droid [\d\.]+; (fp\du?)(?: b|\))/i // Fairphone
+ ], [MODEL, [VENDOR, 'Fairphone'], [TYPE, MOBILE]], [
+ /((?:tegranote|shield t(?!.+d tv))[\w- ]*?)(?: b|\))/i // Nvidia Tablets
+ ], [MODEL, [VENDOR, NVIDIA], [TYPE, TABLET]], [
+ /(sprint) (\w+)/i // Sprint Phones
+ ], [VENDOR, MODEL, [TYPE, MOBILE]], [
+ /(kin\.[onetw]{3})/i // Microsoft Kin
+ ], [[MODEL, /\./g, ' '], [VENDOR, MICROSOFT], [TYPE, MOBILE]], [
+ /droid.+; ([c6]+|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i // Zebra
+ ], [MODEL, [VENDOR, ZEBRA], [TYPE, TABLET]], [
+ /droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i
+ ], [MODEL, [VENDOR, ZEBRA], [TYPE, MOBILE]], [
+
+ ///////////////////
+ // SMARTTVS
+ ///////////////////
+
+ /smart-tv.+(samsung)/i // Samsung
+ ], [VENDOR, [TYPE, SMARTTV]], [
+ /hbbtv.+maple;(\d+)/i
+ ], [[MODEL, /^/, 'SmartTV'], [VENDOR, SAMSUNG], [TYPE, SMARTTV]], [
+ /(vizio)(?: |.+model\/)(\w+-\w+)/i, // Vizio
+ /tcast.+(lg)e?. ([-\w]+)/i // LG SmartTV
+ ], [VENDOR, MODEL, [TYPE, SMARTTV]], [
+ /(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i
+ ], [[VENDOR, LG], [TYPE, SMARTTV]], [
+ /(apple) ?tv/i // Apple TV
+ ], [VENDOR, [MODEL, APPLE+' TV'], [TYPE, SMARTTV]], [
+ /crkey.*devicetype\/chromecast/i // Google Chromecast Third Generation
+ ], [[MODEL, CHROMECAST+' Third Generation'], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /crkey.*devicetype\/([^/]*)/i // Google Chromecast with specific device type
+ ], [[MODEL, /^/, 'Chromecast '], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /fuchsia.*crkey/i // Google Chromecast Nest Hub
+ ], [[MODEL, CHROMECAST+' Nest Hub'], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /crkey/i // Google Chromecast, Linux-based or unknown
+ ], [[MODEL, CHROMECAST], [VENDOR, GOOGLE], [TYPE, SMARTTV]], [
+ /(portaltv)/i // Facebook Portal TV
+ ], [MODEL, [VENDOR, FACEBOOK], [TYPE, SMARTTV]], [
+ /droid.+aft(\w+)( bui|\))/i // Fire TV
+ ], [MODEL, [VENDOR, AMAZON], [TYPE, SMARTTV]], [
+ /(shield \w+ tv)/i // Nvidia Shield TV
+ ], [MODEL, [VENDOR, NVIDIA], [TYPE, SMARTTV]], [
+ /\(dtv[\);].+(aquos)/i,
+ /(aquos-tv[\w ]+)\)/i // Sharp
+ ], [MODEL, [VENDOR, SHARP], [TYPE, SMARTTV]],[
+ /(bravia[\w ]+)( bui|\))/i // Sony
+ ], [MODEL, [VENDOR, SONY], [TYPE, SMARTTV]], [
+ /(mi(tv|box)-?\w+) bui/i // Xiaomi
+ ], [MODEL, [VENDOR, XIAOMI], [TYPE, SMARTTV]], [
+ /Hbbtv.*(technisat) (.*);/i // TechniSAT
+ ], [VENDOR, MODEL, [TYPE, SMARTTV]], [
+ /\b(roku)[\dx]*[\)\/]((?:dvp-)?[\d\.]*)/i, // Roku
+ /hbbtv\/\d+\.\d+\.\d+ +\([\w\+ ]*; *([\w\d][^;]*);([^;]*)/i // HbbTV devices
+ ], [[VENDOR, /.+\/(\w+)/, '$1', strMapper, {'LG':'lge'}], [MODEL, trim], [TYPE, SMARTTV]], [
+ // SmartTV from Unidentified Vendors
+ /droid.+; ([\w- ]+) (?:android tv|smart[- ]?tv)/i
+ ], [MODEL, [TYPE, SMARTTV]], [
+ /\b(android tv|smart[- ]?tv|opera tv|tv; rv:|large screen[\w ]+safari)\b/i
+ ], [[TYPE, SMARTTV]], [
+
+ ///////////////////
+ // CONSOLES
+ ///////////////////
+
+ /(playstation \w+)/i // Playstation
+ ], [MODEL, [VENDOR, SONY], [TYPE, CONSOLE]], [
+ /\b(xbox(?: one)?(?!; xbox))[\); ]/i // Microsoft Xbox
+ ], [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]], [
+
+ ///////////////////
+ // WEARABLES
+ ///////////////////
+
+ /\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
+ ], [VENDOR, MODEL, [TYPE, WEARABLE]], [
+ /(ow(?:19|20)?we?[1-3]{1,3})/i // Oppo Watch
+ ], [MODEL, [VENDOR, OPPO], [TYPE, WEARABLE]], [
+ /(watch)(?: ?os[,\/]|\d,\d\/)[\d\.]+/i // Apple Watch
+ ], [MODEL, [VENDOR, APPLE], [TYPE, WEARABLE]], [
+ /(opwwe\d{3})/i // OnePlus Watch
+ ], [MODEL, [VENDOR, ONEPLUS], [TYPE, WEARABLE]], [
+ /(moto 360)/i // Motorola 360
+ ], [MODEL, [VENDOR, MOTOROLA], [TYPE, WEARABLE]], [
+ /(smartwatch 3)/i // Sony SmartWatch
+ ], [MODEL, [VENDOR, SONY], [TYPE, WEARABLE]], [
+ /(g watch r)/i // LG G Watch R
+ ], [MODEL, [VENDOR, LG], [TYPE, WEARABLE]], [
+ /droid.+; (wt63?0{2,3})\)/i
+ ], [MODEL, [VENDOR, ZEBRA], [TYPE, WEARABLE]], [
+
+ ///////////////////
+ // XR
+ ///////////////////
+
+ /droid.+; (glass) \d/i // Google Glass
+ ], [MODEL, [VENDOR, GOOGLE], [TYPE, XR]], [
+ /(pico) (4|neo3(?: link|pro)?)/i // Pico
+ ], [VENDOR, MODEL, [TYPE, XR]], [
+ /(quest( \d| pro)?s?).+vr/i // Meta Quest
+ ], [MODEL, [VENDOR, FACEBOOK], [TYPE, XR]], [
+ /mobile vr; rv.+firefox/i // Unidentifiable VR device using Firefox Reality / Wolvic
+ ], [[TYPE, XR]], [
+
+ ///////////////////
+ // EMBEDDED
+ ///////////////////
+
+ /(tesla)(?: qtcarbrowser|\/[-\w\.]+)/i // Tesla
+ ], [VENDOR, [TYPE, EMBEDDED]], [
+ /(aeobc)\b/i // Echo Dot
+ ], [MODEL, [VENDOR, AMAZON], [TYPE, EMBEDDED]], [
+ /(homepod).+mac os/i // Apple HomePod
+ ], [MODEL, [VENDOR, APPLE], [TYPE, EMBEDDED]], [
+ /windows iot/i // Unidentifiable embedded device using Windows IoT
+ ], [[TYPE, EMBEDDED]], [
+
+ ////////////////////
+ // MIXED (GENERIC)
+ ///////////////////
+
+ /droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew).+?(mobile|vr|\d) safari/i
+ ], [MODEL, [TYPE, strMapper, { 'mobile' : 'Mobile', 'xr' : 'VR', '*' : TABLET }]], [
+ /\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i // Unidentifiable Tablet
+ ], [[TYPE, TABLET]], [
+ /(phone|mobile(?:[;\/]| [ \w\/\.]*safari)|pda(?=.+windows ce))/i // Unidentifiable Mobile
+ ], [[TYPE, MOBILE]], [
+ /droid .+?; ([\w\. -]+)( bui|\))/i // Generic Android Device
+ ], [MODEL, [VENDOR, 'Generic']]
+ ],
+
+ engine : [[
+
+ /windows.+ edge\/([\w\.]+)/i // EdgeHTML
+ ], [VERSION, [NAME, EDGE+'HTML']], [
+
+ /(arkweb)\/([\w\.]+)/i // ArkWeb
+ ], [NAME, VERSION], [
+
+ /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i // Blink
+ ], [VERSION, [NAME, 'Blink']], [
+
+ /(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
+ /(icab)[\/ ]([23]\.[\d\.]+)/i, // iCab
+
+ /\b(libweb)/i // LibWeb
+ ], [NAME, VERSION], [
+ /ladybird\//i
+ ], [[NAME, 'LibWeb']], [
+
+ /rv\:([\w\.]{1,9})\b.+(gecko)/i // Gecko
+ ], [VERSION, NAME]
+ ],
+
+ os : [[
+
+ // Windows
+ /(windows nt) (6\.[23]); arm/i // Windows RT
+ ], [[NAME, /N/, 'R'], [VERSION, strMapper, windowsVersionMap]], [
+ /(windows (?:phone|mobile|iot))(?: os)?[\/ ]?([\d\.]*( se)?)/i, // Windows IoT/Mobile/Phone
+ // Windows NT/3.1/95/98/ME/2000/XP/Vista/7/8/8.1/10/11
+ /(windows)[\/ ](1[01]|2000|3\.1|7|8(\.1)?|9[58]|me|server 20\d\d( r2)?|vista|xp)/i
+ ], [NAME, VERSION], [
+ /windows nt ?([\d\.\)]*)(?!.+xbox)/i,
+ /\bwin(?=3| ?9|n)(?:nt| 9x )?([\d\.;]*)/i
+ ], [[VERSION, /(;|\))/g, '', strMapper, windowsVersionMap], [NAME, WINDOWS]], [
+ /(windows ce)\/?([\d\.]*)/i // Windows CE
+ ], [NAME, VERSION], [
+
+ // iOS/macOS
+ /[adehimnop]{4,7}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
+ /(?:ios;fbsv\/|iphone.+ios[\/ ])([\d\.]+)/i,
+ /cfnetwork\/.+darwin/i
+ ], [[VERSION, /_/g, '.'], [NAME, 'iOS']], [
+ /(mac os x) ?([\w\. ]*)/i,
+ /(macintosh|mac_powerpc\b)(?!.+(haiku|morphos))/i // Mac OS
+ ], [[NAME, 'macOS'], [VERSION, /_/g, '.']], [
+
+ // Google Chromecast
+ /android ([\d\.]+).*crkey/i // Google Chromecast, Android-based
+ ], [VERSION, [NAME, CHROMECAST + ' Android']], [
+ /fuchsia.*crkey\/([\d\.]+)/i // Google Chromecast, Fuchsia-based
+ ], [VERSION, [NAME, CHROMECAST + ' Fuchsia']], [
+ /crkey\/([\d\.]+).*devicetype\/smartspeaker/i // Google Chromecast, Linux-based Smart Speaker
+ ], [VERSION, [NAME, CHROMECAST + ' SmartSpeaker']], [
+ /linux.*crkey\/([\d\.]+)/i // Google Chromecast, Legacy Linux-based
+ ], [VERSION, [NAME, CHROMECAST + ' Linux']], [
+ /crkey\/([\d\.]+)/i // Google Chromecast, unknown
+ ], [VERSION, [NAME, CHROMECAST]], [
+
+ // Mobile OSes
+ /droid ([\w\.]+)\b.+(android[- ]x86)/i // Android-x86
+ ], [VERSION, NAME], [
+ /(ubuntu) ([\w\.]+) like android/i // Ubuntu Touch
+ ], [[NAME, /(.+)/, '$1 Touch'], VERSION], [
+ /(harmonyos)[\/ ]?([\d\.]*)/i, // HarmonyOS
+ // Android/Blackberry/WebOS/QNX/Bada/RIM/KaiOS/Maemo/MeeGo/S40/Sailfish OS/OpenHarmony/Tizen
+ /(android|bada|blackberry|kaios|maemo|meego|openharmony|qnx|rim tablet os|sailfish|series40|symbian|tizen)\w*[-\/\.; ]?([\d\.]*)/i
+ ], [NAME, VERSION], [
+ /\(bb(10);/i // BlackBerry 10
+ ], [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
+ ], [VERSION, [NAME, FIREFOX+' OS']], [
+ /\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i, // WebOS
+ /webos(?:[ \/]?|\.tv-20(?=2[2-9]))(\d[\d\.]*)/i
+ ], [VERSION, [NAME, 'webOS']], [
+ /web0s;.+?(?:chr[o0]me|safari)\/(\d+)/i
+ // https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine
+ ], [[VERSION, strMapper, {'25':'120','24':'108','23':'94','22':'87','6':'79','5':'68','4':'53','3':'38','2':'538','1':'537','*':'TV'}], [NAME, 'webOS']], [
+ /watch(?: ?os[,\/]|\d,\d\/)([\d\.]+)/i // watchOS
+ ], [VERSION, [NAME, 'watchOS']], [
+
+ // Google ChromeOS
+ /(cros) [\w]+(?:\)| ([\w\.]+)\b)/i // Chromium OS
+ ], [[NAME, "Chrome OS"], VERSION],[
+
+ // Smart TVs
+ /panasonic;(viera)/i, // Panasonic Viera
+ /(netrange)mmh/i, // Netrange
+ /(nettv)\/(\d+\.[\w\.]+)/i, // NetTV
+
+ // Console
+ /(nintendo|playstation) (\w+)/i, // Nintendo/Playstation
+ /(xbox); +xbox ([^\);]+)/i, // Microsoft Xbox (360, One, X, S, Series X, Series S)
+ /(pico) .+os([\w\.]+)/i, // Pico
+
+ // Other
+ /\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i, // Joli/Palm
+ /linux.+(mint)[\/\(\) ]?([\w\.]*)/i, // Mint
+ /(mageia|vectorlinux|fuchsia|arcaos|arch(?= ?linux))[;l ]([\d\.]*)/i, // Mageia/VectorLinux/Fuchsia/ArcaOS/Arch
+ /([kxln]?ubuntu|debian|suse|opensuse|gentoo|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire|knoppix)(?: gnu[\/ ]linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i,
+ // Ubuntu/Debian/SUSE/Gentoo/Slackware/Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus/Raspbian/Plan9/Minix/RISCOS/Contiki/Deepin/Manjaro/elementary/Sabayon/Linspire/Knoppix
+ /((?:open)?solaris)[-\/ ]?([\w\.]*)/i, // Solaris
+ /\b(aix)[; ]([1-9\.]{0,4})/i, // AIX
+ /(hurd|linux|morphos)(?: (?:arm|x86|ppc)\w*| ?)([\w\.]*)/i, // Hurd/Linux/MorphOS
+ /(gnu) ?([\w\.]*)/i, // GNU
+ /\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i, // FreeBSD/NetBSD/OpenBSD/PC-BSD/GhostBSD/DragonFly
+ /(haiku) ?(r\d)?/i // Haiku
+ ], [NAME, VERSION], [
+ /(sunos) ?([\d\.]*)/i // Solaris
+ ], [[NAME, 'Solaris'], VERSION], [
+ /\b(beos|os\/2|amigaos|openvms|hp-ux|serenityos)/i, // BeOS/OS2/AmigaOS/OpenVMS/HP-UX/SerenityOS
+ /(unix) ?([\w\.]*)/i // UNIX
+ ], [NAME, VERSION]
+ ]
+ };
+
+ /////////////////
+ // Factories
+ ////////////////
+
+ 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]]
+ ]);
+ setProps.call(props.isIgnore, [
+ [UA_BROWSER, [VERSION, MAJOR]],
+ [UA_ENGINE, [VERSION]],
+ [UA_OS, [VERSION]]
+ ]);
+ setProps.call(props.isIgnoreRgx, [
+ [UA_BROWSER, / ?browser$/i],
+ [UA_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]]
+ ]);
+ return props;
+ })();
+
+ var createIData = function (item, itemType) {
+
+ var init_props = defaultProps.init[itemType],
+ is_ignoreProps = defaultProps.isIgnore[itemType] || 0,
+ is_ignoreRgx = defaultProps.isIgnoreRgx[itemType] || 0,
+ toString_props = defaultProps.toString[itemType] || 0;
+
+ function IData () {
+ setProps.call(this, init_props);
+ }
+
+ IData.prototype.getItem = function () {
+ return item;
+ };
+
+ IData.prototype.withClientHints = function () {
+
+ // nodejs / non-client-hints browsers
+ if (!NAVIGATOR_UADATA) {
+ return item
+ .parseCH()
+ .get();
+ }
+
+ // browsers based on chromium 85+
+ return NAVIGATOR_UADATA
+ .getHighEntropyValues(CH_ALL_VALUES)
+ .then(function (res) {
+ return item
+ .setCH(new UACHData(res, false))
+ .parseCH()
+ .get();
+ });
+ };
+
+ IData.prototype.withFeatureCheck = function () {
+ return item.detectFeature().get();
+ };
+
+ if (itemType != UA_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) {
+ is = !is;
+ break;
+ }
+ }
+ return is;
+ };
+ IData.prototype.toString = function () {
+ var str = EMPTY;
+ for (var i in toString_props) {
+ if (typeof(this[toString_props[i]]) !== UNDEF_TYPE) {
+ str += (str ? ' ' : EMPTY) + this[toString_props[i]];
+ }
+ }
+ return str || UNDEF_TYPE;
+ };
+ }
+
+ 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];
+ }
+ }
+ };
+ IDataResolve.prototype = {
+ is : IData.prototype.is,
+ toString : IData.prototype.toString
+ };
+ var resolveData = new IDataResolve();
+ cb(resolveData);
+ return resolveData;
+ };
+ }
+
+ return new IData();
+ };
+
+ /////////////////
+ // Constructor
+ ////////////////
+
+ function UACHData (uach, isHttpUACH) {
+ uach = uach || {};
+ 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])]
+ ]);
+ } else {
+ for (var prop in uach) {
+ if(this.hasOwnProperty(prop) && typeof uach[prop] !== UNDEF_TYPE) 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= 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],
+ ['uaCH', uaCH],
+ ['rgxMap', rgxMap],
+ ['data', createIData(this, itemType)]
+ ]);
+
+ return this;
+ }
+
+ function UAParser (ua, extensions, headers) {
+
+ if (typeof ua === OBJ_TYPE) {
+ if (isExtensions(ua, true)) {
+ if (typeof extensions === OBJ_TYPE) {
+ headers = extensions; // case UAParser(extensions, headers)
+ }
+ extensions = ua; // case UAParser(extensions)
+ } else {
+ headers = ua; // case UAParser(headers)
+ extensions = undefined;
+ }
+ ua = undefined;
+ } else if (typeof ua === STR_TYPE && !isExtensions(extensions, true)) {
+ headers = extensions; // case UAParser(ua, headers)
+ extensions = undefined;
+ }
+
+ // Convert Headers object into a plain object
+ if (headers && typeof headers.append === FUNC_TYPE) {
+ var kv = {};
+ headers.forEach(function (v, k) { kv[k] = v; });
+ headers = kv;
+ }
+
+ if (!(this instanceof UAParser)) {
+ return new UAParser(ua, extensions, headers).getResult();
+ }
+
+ var userAgent = typeof ua === STR_TYPE ? 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
+
+ httpUACH = new UACHData(headers, true),
+ regexMap = extensions ?
+ extend(defaultRegexes, extensions) :
+ defaultRegexes,
+
+ createItemFunc = function (itemType) {
+ if (itemType == UA_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())
+ .get();
+ };
+ } else {
+ return function () {
+ return new UAItem(itemType, userAgent, regexMap[itemType], httpUACH)
+ .parseUA()
+ .get();
+ };
+ }
+ };
+
+ // 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)],
+ ['getUA', function () { return userAgent; }],
+ ['setUA', function (ua) {
+ if (isString(ua))
+ userAgent = ua.length > UA_MAX_LENGTH ? trim(ua, UA_MAX_LENGTH) : ua;
+ return this;
+ }]
+ ])
+ .setUA(userAgent);
+
+ return this;
+ }
+
+ UAParser.VERSION = LIBVERSION;
+ UAParser.BROWSER = enumerize([NAME, VERSION, MAJOR, TYPE]);
+ UAParser.CPU = enumerize([ARCHITECTURE]);
+ UAParser.DEVICE = enumerize([MODEL, VENDOR, TYPE, CONSOLE, MOBILE, SMARTTV, TABLET, WEARABLE, EMBEDDED]);
+ UAParser.ENGINE = UAParser.OS = enumerize([NAME, VERSION]);
+
+ export {UAParser};
\ No newline at end of file
diff --git a/src/ua-parser.js b/src/ua-parser.js
deleted file mode 100755
index 691884903..000000000
--- a/src/ua-parser.js
+++ /dev/null
@@ -1,1190 +0,0 @@
-/////////////////////////////////////////////////////////////////////////////////
-/* UAParser.js v0.7.33
- Copyright Š 2012-2021 Faisal Salman
- MIT License */ /*
- Detect Browser, Engine, OS, CPU, and Device type/model from User-Agent data.
- Supports browser & node.js environment.
- Demo : https://faisalman.github.io/ua-parser-js
- Source : https://github.com/faisalman/ua-parser-js */
-/////////////////////////////////////////////////////////////////////////////////
-
-(function (window, undefined) {
- "use strict";
-
- //////////////
- // Constants
- /////////////
-
- var LIBVERSION = "0.7.33",
- EMPTY = "",
- UNKNOWN = "?",
- FUNC_TYPE = "function",
- UNDEF_TYPE = "undefined",
- OBJ_TYPE = "object",
- STR_TYPE = "string",
- MAJOR = "major",
- MODEL = "model",
- NAME = "name",
- TYPE = "type",
- VENDOR = "vendor",
- VERSION = "version",
- ARCHITECTURE = "architecture",
- CONSOLE = "console",
- MOBILE = "mobile",
- TABLET = "tablet",
- SMARTTV = "smarttv",
- WEARABLE = "wearable",
- EMBEDDED = "embedded",
- UA_MAX_LENGTH = 350;
-
- var AMAZON = "Amazon",
- APPLE = "Apple",
- ASUS = "ASUS",
- BLACKBERRY = "BlackBerry",
- BROWSER = "Browser",
- CHROME = "Chrome",
- EDGE = "Edge",
- FIREFOX = "Firefox",
- GOOGLE = "Google",
- HUAWEI = "Huawei",
- LG = "LG",
- MICROSOFT = "Microsoft",
- MOTOROLA = "Motorola",
- OPERA = "Opera",
- SAMSUNG = "Samsung",
- SHARP = "Sharp",
- SONY = "Sony",
- XIAOMI = "Xiaomi",
- ZEBRA = "Zebra",
- FACEBOOK = "Facebook";
-
- ///////////
- // Helper
- //////////
-
- var extend = function (regexes, extensions) {
- var mergedRegexes = {};
- for (var i in regexes) {
- if (extensions[i] && extensions[i].length % 2 === 0) {
- mergedRegexes[i] = extensions[i].concat(regexes[i]);
- } else {
- mergedRegexes[i] = regexes[i];
- }
- }
- return mergedRegexes;
- },
- enumerize = function (arr) {
- var enums = {};
- for (var i = 0; i < arr.length; i++) {
- enums[arr[i].toUpperCase()] = arr[i];
- }
- return enums;
- },
- has = function (str1, str2) {
- return typeof str1 === STR_TYPE ? lowerize(str2).indexOf(lowerize(str1)) !== -1 : false;
- },
- lowerize = function (str) {
- return str.toLowerCase();
- },
- majorize = function (version) {
- return typeof version === STR_TYPE ? version.replace(/[^\d\.]/g, EMPTY).split(".")[0] : undefined;
- },
- trim = function (str, len) {
- if (typeof str === STR_TYPE) {
- str = str.replace(/^\s\s*/, EMPTY);
- return typeof len === UNDEF_TYPE ? str : str.substring(0, UA_MAX_LENGTH);
- }
- };
-
- ///////////////
- // Map helper
- //////////////
-
- var rgxMapper = function (ua, arrays) {
- var i = 0,
- j,
- k,
- p,
- q,
- matches,
- match;
-
- // loop through all regexes maps
- while (i < arrays.length && !matches) {
- var regex = arrays[i], // even sequence (0,2,4,..)
- props = arrays[i + 1]; // odd sequence (1,3,5,..)
- j = k = 0;
-
- // try matching uastring with regexes
- while (j < regex.length && !matches) {
- matches = regex[j++].exec(ua);
-
- if (!!matches) {
- for (p = 0; p < props.length; p++) {
- match = matches[++k];
- q = props[p];
- // check if given property is actually array
- if (typeof q === OBJ_TYPE && q.length > 0) {
- if (q.length === 2) {
- if (typeof q[1] == FUNC_TYPE) {
- // assign modified match
- this[q[0]] = q[1].call(this, match);
- } else {
- // assign given value, ignore regex match
- this[q[0]] = q[1];
- }
- } else if (q.length === 3) {
- // check whether function or regex
- if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
- // call function (usually string mapper)
- this[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
- } else {
- // sanitize match using given regex
- this[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
- }
- } else if (q.length === 4) {
- this[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
- }
- } else {
- this[q] = match ? match : undefined;
- }
- }
- }
- }
- i += 2;
- }
- },
- strMapper = function (str, map) {
- for (var i in map) {
- // check if current value is array
- if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
- for (var j = 0; j < map[i].length; j++) {
- if (has(map[i][j], str)) {
- return i === UNKNOWN ? undefined : i;
- }
- }
- } else if (has(map[i], str)) {
- return i === UNKNOWN ? undefined : i;
- }
- }
- return str;
- };
-
- ///////////////
- // String map
- //////////////
-
- // Safari < 3.0
- var oldSafariMap = {
- "1.0": "/8",
- 1.2: "/1",
- 1.3: "/3",
- "2.0": "/412",
- "2.0.2": "/416",
- "2.0.3": "/417",
- "2.0.4": "/419",
- "?": "/"
- },
- windowsVersionMap = {
- ME: "4.90",
- "NT 3.11": "NT3.51",
- "NT 4.0": "NT4.0",
- 2000: "NT 5.0",
- XP: ["NT 5.1", "NT 5.2"],
- Vista: "NT 6.0",
- 7: "NT 6.1",
- 8: "NT 6.2",
- 8.1: "NT 6.3",
- 10: ["NT 6.4", "NT 10.0"],
- RT: "ARM"
- };
-
- //////////////
- // Regex map
- /////////////
-
- var regexes = {
- browser: [
- [
- /\b(?:crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
- ],
- [VERSION, [NAME, "Chrome"]],
- [
- /edg(?:e|ios|a)?\/([\w\.]+)/i // Microsoft Edge
- ],
- [VERSION, [NAME, "Edge"]],
- [
- // Presto based
- /(opera mini)\/([-\w\.]+)/i, // Opera Mini
- /(opera [mobiletab]{3,6})\b.+version\/([-\w\.]+)/i, // Opera Mobi/Tablet
- /(opera)(?:.+version\/|[\/ ]+)([\w\.]+)/i // Opera
- ],
- [NAME, VERSION],
- [
- /opios[\/ ]+([\w\.]+)/i // Opera mini on iphone >= 8.0
- ],
- [VERSION, [NAME, OPERA + " Mini"]],
- [
- /\bopr\/([\w\.]+)/i // Opera Webkit
- ],
- [VERSION, [NAME, OPERA]],
- [
- // Mixed
- /(kindle)\/([\w\.]+)/i, // Kindle
- /(lunascape|maxthon|netfront|jasmine|blazer)[\/ ]?([\w\.]*)/i, // Lunascape/Maxthon/Netfront/Jasmine/Blazer
- // Trident based
- /(avant |iemobile|slim)(?:browser)?[\/ ]?([\w\.]*)/i, // Avant/IEMobile/SlimBrowser
- /(ba?idubrowser)[\/ ]?([\w\.]+)/i, // Baidu Browser
- /(?:ms|\()(ie) ([\w\.]+)/i, // Internet Explorer
-
- // Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon
- /(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon|rekonq|puffin|brave|whale|qqbrowserlite|qq|duckduckgo)\/([-\w\.]+)/i,
- // Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ, aka ShouQ
- /(weibo)__([\d\.]+)/i // Weibo
- ],
- [NAME, VERSION],
- [
- /(?:\buc? ?browser|(?:juc.+)ucweb)[\/ ]?([\w\.]+)/i // UCBrowser
- ],
- [VERSION, [NAME, "UC" + BROWSER]],
- [
- /microm.+\bqbcore\/([\w\.]+)/i, // WeChat Desktop for Windows Built-in Browser
- /\bqbcore\/([\w\.]+).+microm/i
- ],
- [VERSION, [NAME, "WeChat(Win) Desktop"]],
- [
- /micromessenger\/([\w\.]+)/i // WeChat
- ],
- [VERSION, [NAME, "WeChat"]],
- [
- /konqueror\/([\w\.]+)/i // Konqueror
- ],
- [VERSION, [NAME, "Konqueror"]],
- [
- /trident.+rv[: ]([\w\.]{1,9})\b.+like gecko/i // IE11
- ],
- [VERSION, [NAME, "IE"]],
- [
- /yabrowser\/([\w\.]+)/i // Yandex
- ],
- [VERSION, [NAME, "Yandex"]],
- [
- /(avast|avg)\/([\w\.]+)/i // Avast/AVG Secure Browser
- ],
- [[NAME, /(.+)/, "$1 Secure " + BROWSER], VERSION],
- [
- /\bfocus\/([\w\.]+)/i // Firefox Focus
- ],
- [VERSION, [NAME, FIREFOX + " Focus"]],
- [
- /\bopt\/([\w\.]+)/i // Opera Touch
- ],
- [VERSION, [NAME, OPERA + " Touch"]],
- [
- /coc_coc\w+\/([\w\.]+)/i // Coc Coc Browser
- ],
- [VERSION, [NAME, "Coc Coc"]],
- [
- /dolfin\/([\w\.]+)/i // Dolphin
- ],
- [VERSION, [NAME, "Dolphin"]],
- [
- /coast\/([\w\.]+)/i // Opera Coast
- ],
- [VERSION, [NAME, OPERA + " Coast"]],
- [
- /miuibrowser\/([\w\.]+)/i // MIUI Browser
- ],
- [VERSION, [NAME, "MIUI " + BROWSER]],
- [
- /fxios\/([-\w\.]+)/i // Firefox for iOS
- ],
- [VERSION, [NAME, FIREFOX]],
- [
- /\bqihu|(qi?ho?o?|360)browser/i // 360
- ],
- [[NAME, "360 " + BROWSER]],
- [/(oculus|samsung|sailfish|huawei)browser\/([\w\.]+)/i],
- [[NAME, /(.+)/, "$1 " + BROWSER], VERSION],
- [
- // Oculus/Samsung/Sailfish/Huawei Browser
- /(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon
- ],
- [[NAME, /_/g, " "], VERSION],
- [
- /(electron)\/([\w\.]+) safari/i, // Electron-based App
- /(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w\.]+))/i, // Tesla
- /m?(qqbrowser|baiduboxapp|2345Explorer)[\/ ]?([\w\.]+)/i // QQBrowser/Baidu App/2345 Browser
- ],
- [NAME, VERSION],
- [
- /(metasr)[\/ ]?([\w\.]+)/i, // SouGouBrowser
- /(lbbrowser)/i, // LieBao Browser
- /\[(linkedin)app\]/i // LinkedIn App for iOS & Android
- ],
- [NAME],
- [
- // WebView
- /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w\.]+);)/i // Facebook App for iOS & Android
- ],
- [[NAME, FACEBOOK], VERSION],
- [
- /safari (line)\/([\w\.]+)/i, // Line App for iOS
- /\b(line)\/([\w\.]+)\/iab/i, // Line App for Android
- /(chromium|instagram)[\/ ]([-\w\.]+)/i // Chromium/Instagram
- ],
- [NAME, VERSION],
- [
- /\bgsa\/([\w\.]+) .*safari\//i // Google Search Appliance on iOS
- ],
- [VERSION, [NAME, "GSA"]],
- [
- /headlesschrome(?:\/([\w\.]+)| )/i // Chrome Headless
- ],
- [VERSION, [NAME, CHROME + " Headless"]],
- [
- / wv\).+(chrome)\/([\w\.]+)/i // Chrome WebView
- ],
- [[NAME, CHROME + " WebView"], VERSION],
- [
- /droid.+ version\/([\w\.]+)\b.+(?:mobile safari|safari)/i // Android Browser
- ],
- [VERSION, [NAME, "Android " + BROWSER]],
- [
- /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w\.]+)/i // Chrome/OmniWeb/Arora/Tizen/Nokia
- ],
- [NAME, VERSION],
- [
- /version\/([\w\.\,]+) .*mobile\/\w+ (safari)/i // Mobile Safari
- ],
- [VERSION, [NAME, "Mobile Safari"]],
- [
- /version\/([\w(\.|\,)]+) .*(mobile ?safari|safari)/i // Safari & Safari Mobile
- ],
- [VERSION, NAME],
- [
- /webkit.+?(mobile ?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
- ],
- [NAME, [VERSION, strMapper, oldSafariMap]],
- [/(webkit|khtml)\/([\w\.]+)/i],
- [NAME, VERSION],
- [
- // Gecko based
- /(navigator|netscape\d?)\/([-\w\.]+)/i // Netscape
- ],
- [[NAME, "Netscape"], VERSION],
- [
- /mobile vr; rv:([\w\.]+)\).+firefox/i // Firefox Reality
- ],
- [VERSION, [NAME, FIREFOX + " Reality"]],
- [
- /ekiohf.+(flow)\/([\w\.]+)/i, // Flow
- /(swiftfox)/i, // Swiftfox
- /(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror|klar)[\/ ]?([\w\.\+]+)/i,
- // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror/Klar
- /(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|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
-
- // Other
- /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir|obigo|mosaic|(?:go|ice|up)[\. ]?browser)[-\/ ]?v?([\w\.]+)/i,
- // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir/Obigo/Mosaic/Go/ICE/UP.Browser
- /(links) \(([\w\.]+)/i // Links
- ],
- [NAME, VERSION],
- [
- /(cobalt)\/([\w\.]+)/i // Cobalt
- ],
- [NAME, [VERSION, /master.|lts./, ""]]
- ],
-
- cpu: [
- [
- /(?:(amd|x(?:(?:86|64)[-_])?|wow|win)64)[;\)]/i // AMD64 (x64)
- ],
- [[ARCHITECTURE, "amd64"]],
- [
- /(ia32(?=;))/i // IA32 (quicktime)
- ],
- [[ARCHITECTURE, lowerize]],
- [
- /((?:i[346]|x)86)[;\)]/i // IA32 (x86)
- ],
- [[ARCHITECTURE, "ia32"]],
- [
- /\b(aarch64|arm(v?8e?l?|_?64))\b/i // ARM64
- ],
- [[ARCHITECTURE, "arm64"]],
- [
- /\b(arm(?:v[67])?ht?n?[fl]p?)\b/i // ARMHF
- ],
- [[ARCHITECTURE, "armhf"]],
- [
- // PocketPC mistakenly identified as PowerPC
- /windows (ce|mobile); ppc;/i
- ],
- [[ARCHITECTURE, "arm"]],
- [
- /((?:ppc|powerpc)(?:64)?)(?: mac|;|\))/i // PowerPC
- ],
- [[ARCHITECTURE, /ower/, EMPTY, lowerize]],
- [
- /(sun4\w)[;\)]/i // SPARC
- ],
- [[ARCHITECTURE, "sparc"]],
- [
- /((?:avr32|ia64(?=;))|68k(?=\))|\barm(?=v(?:[1-7]|[5-7]1)l?|;|eabi)|(?=atmel )avr|(?:irix|mips|sparc)(?:64)?\b|pa-risc)/i
- // IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
- ],
- [[ARCHITECTURE, lowerize]]
- ],
-
- device: [
- [
- //////////////////////////
- // MOBILES & TABLETS
- // Ordered by popularity
- /////////////////////////
-
- // Samsung
- /\b(sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i
- ],
- [MODEL, [VENDOR, SAMSUNG], [TYPE, TABLET]],
- [
- /\b((?:s[cgp]h|gt|sm)-\w+|galaxy nexus)/i,
- /samsung[- ]([-\w]+)/i,
- /sec-(sgh\w+)/i
- ],
- [MODEL, [VENDOR, SAMSUNG], [TYPE, MOBILE]],
- [
- // Apple
- /((ipod|iphone)\d+,\d+)/i // iPod/iPhone model
- ],
- [MODEL, [VENDOR, APPLE], [TYPE, MOBILE]],
- [
- /(ipad\d+,\d+)/i // iPad model
- ],
- [MODEL, [VENDOR, APPLE], [TYPE, TABLET]],
- [
- /\((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
- ],
- [MODEL, [VENDOR, APPLE], [TYPE, TABLET]],
- [/(macintosh);/i],
- [MODEL, [VENDOR, APPLE]],
- [
- // Huawei
- /\b((?:ag[rs][23]?|bah2?|sht?|btv)-a?[lw]\d{2})\b(?!.+d\/s)/i
- ],
- [MODEL, [VENDOR, HUAWEI], [TYPE, TABLET]],
- [
- /(?:huawei|honor)([-\w ]+)[;\)]/i,
- /\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][012359c][adn]?)\b(?!.+d\/s)/i
- ],
- [MODEL, [VENDOR, HUAWEI], [TYPE, MOBILE]],
- [
- // Xiaomi
- /\b(poco[\w ]+)(?: 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
- /\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note lte|max|cc)?[_ ]?(?:\d?\w?)[_ ]?(?:plus|se|lite)?)(?: bui|\))/i // Xiaomi Mi
- ],
- [
- [MODEL, /_/g, " "],
- [VENDOR, XIAOMI],
- [TYPE, MOBILE]
- ],
- [
- /\b(mi[-_ ]?(?:pad)(?:[\w_ ]+))(?: bui|\))/i // Mi Pad tablets
- ],
- [
- [MODEL, /_/g, " "],
- [VENDOR, XIAOMI],
- [TYPE, TABLET]
- ],
- [
- // OPPO
- /; (\w+) bui.+ oppo/i,
- /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i
- ],
- [MODEL, [VENDOR, "OPPO"], [TYPE, MOBILE]],
- [
- // Vivo
- /vivo (\w+)(?: bui|\))/i,
- /\b(v[12]\d{3}\w?[at])(?: bui|;)/i
- ],
- [MODEL, [VENDOR, "Vivo"], [TYPE, MOBILE]],
- [
- // Realme
- /\b(rmx[12]\d{3})(?: bui|;|\))/i
- ],
- [MODEL, [VENDOR, "Realme"], [TYPE, MOBILE]],
- [
- // Motorola
- /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i,
- /\bmot(?:orola)?[- ](\w*)/i,
- /((?:moto[\w\(\) ]+|xt\d{3,4}|nexus 6)(?= bui|\)))/i
- ],
- [MODEL, [VENDOR, MOTOROLA], [TYPE, MOBILE]],
- [/\b(mz60\d|xoom[2 ]{0,2}) build\//i],
- [MODEL, [VENDOR, MOTOROLA], [TYPE, TABLET]],
- [
- // LG
- /((?=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)\w+)/i,
- /\blg-?([\d\w]+) bui/i
- ],
- [MODEL, [VENDOR, LG], [TYPE, MOBILE]],
- [
- // Lenovo
- /(ideatab[-\w ]+)/i,
- /lenovo ?(s[56]000[-\w]+|tab(?:[\w ]+)|yt[-\d\w]{6}|tb[-\d\w]{6})/i
- ],
- [MODEL, [VENDOR, "Lenovo"], [TYPE, TABLET]],
- [
- // Nokia
- /(?:maemo|nokia).*(n900|lumia \d+)/i,
- /nokia[-_ ]?([-\w\.]*)/i
- ],
- [
- [MODEL, /_/g, " "],
- [VENDOR, "Nokia"],
- [TYPE, MOBILE]
- ],
- [
- // Google
- /(pixel c)\b/i // Google Pixel C
- ],
- [MODEL, [VENDOR, GOOGLE], [TYPE, TABLET]],
- [
- /droid.+; (pixel[\daxl ]{0,6})(?: bui|\))/i // Google Pixel
- ],
- [MODEL, [VENDOR, GOOGLE], [TYPE, MOBILE]],
- [
- // Sony
- /droid.+ (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-a\w[4-7][12])(?= bui|\).+chrome\/(?![1-6]{0,1}\d\.))/i
- ],
- [MODEL, [VENDOR, SONY], [TYPE, MOBILE]],
- [/sony tablet [ps]/i, /\b(?:sony)?sgp\w+(?: bui|\))/i],
- [
- [MODEL, "Xperia Tablet"],
- [VENDOR, SONY],
- [TYPE, TABLET]
- ],
- [
- // OnePlus
- / (kb2005|in20[12]5|be20[12][59])\b/i,
- /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i
- ],
- [MODEL, [VENDOR, "OnePlus"], [TYPE, MOBILE]],
- [
- // Amazon
- /(alexa)webm/i,
- /(kf[a-z]{2}wi)( bui|\))/i, // Kindle Fire without Silk
- /(kf[a-z]+)( bui|\)).+silk\//i // Kindle Fire HD
- ],
- [MODEL, [VENDOR, AMAZON], [TYPE, TABLET]],
- [
- /((?:sd|kf)[0349hijorstuw]+)( bui|\)).+silk\//i // Fire Phone
- ],
- [
- [MODEL, /(.+)/g, "Fire Phone $1"],
- [VENDOR, AMAZON],
- [TYPE, MOBILE]
- ],
- [
- // BlackBerry
- /(playbook);[-\w\),; ]+(rim)/i // BlackBerry PlayBook
- ],
- [MODEL, VENDOR, [TYPE, TABLET]],
- [
- /\b((?:bb[a-f]|st[hv])100-\d)/i,
- /\(bb10; (\w+)/i // BlackBerry 10
- ],
- [MODEL, [VENDOR, BLACKBERRY], [TYPE, MOBILE]],
- [
- // Asus
- /(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i
- ],
- [MODEL, [VENDOR, ASUS], [TYPE, TABLET]],
- [/ (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i],
- [MODEL, [VENDOR, ASUS], [TYPE, MOBILE]],
- [
- // HTC
- /(nexus 9)/i // HTC Nexus 9
- ],
- [MODEL, [VENDOR, "HTC"], [TYPE, TABLET]],
- [
- /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i, // HTC
-
- // ZTE
- /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i,
- /(alcatel|geeksphone|nexian|panasonic|sony(?!-bra))[-_ ]?([-\w]*)/i // Alcatel/GeeksPhone/Nexian/Panasonic/Sony
- ],
- [VENDOR, [MODEL, /_/g, " "], [TYPE, MOBILE]],
- [
- // Acer
- /droid.+; ([ab][1-7]-?[0178a]\d\d?)/i
- ],
- [MODEL, [VENDOR, "Acer"], [TYPE, TABLET]],
- [
- // Meizu
- /droid.+; (m[1-5] note) bui/i,
- /\bmz-([-\w]{2,})/i
- ],
- [MODEL, [VENDOR, "Meizu"], [TYPE, MOBILE]],
- [
- // Sharp
- /\b(sh-?[altvz]?\d\d[a-ekm]?)/i
- ],
- [MODEL, [VENDOR, SHARP], [TYPE, MOBILE]],
- [
- // MIXED
- /(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[-_ ]?([-\w]*)/i,
- // BlackBerry/BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron
- /(hp) ([\w ]+\w)/i, // HP iPAQ
- /(asus)-?(\w+)/i, // Asus
- /(microsoft); (lumia[\w ]+)/i, // Microsoft Lumia
- /(lenovo)[-_ ]?([-\w]+)/i, // Lenovo
- /(jolla)/i, // Jolla
- /(oppo) ?([\w ]+) bui/i // OPPO
- ],
- [VENDOR, MODEL, [TYPE, MOBILE]],
- [
- /(archos) (gamepad2?)/i, // Archos
- /(hp).+(touchpad(?!.+tablet)|tablet)/i, // HP TouchPad
- /(kindle)\/([\w\.]+)/i, // Kindle
- /(nook)[\w ]+build\/(\w+)/i, // Nook
- /(dell) (strea[kpr\d ]*[\dko])/i, // Dell Streak
- /(le[- ]+pan)[- ]+(\w{1,9}) bui/i, // Le Pan Tablets
- /(trinity)[- ]*(t\d{3}) bui/i, // Trinity Tablets
- /(gigaset)[- ]+(q\w{1,9}) bui/i, // Gigaset Tablets
- /(vodafone) ([\w ]+)(?:\)| bui)/i // Vodafone
- ],
- [VENDOR, MODEL, [TYPE, TABLET]],
- [
- /(surface duo)/i // Surface Duo
- ],
- [MODEL, [VENDOR, MICROSOFT], [TYPE, TABLET]],
- [
- /droid [\d\.]+; (fp\du?)(?: b|\))/i // Fairphone
- ],
- [MODEL, [VENDOR, "Fairphone"], [TYPE, MOBILE]],
- [
- /(u304aa)/i // AT&T
- ],
- [MODEL, [VENDOR, "AT&T"], [TYPE, MOBILE]],
- [
- /\bsie-(\w*)/i // Siemens
- ],
- [MODEL, [VENDOR, "Siemens"], [TYPE, MOBILE]],
- [
- /\b(rct\w+) b/i // RCA Tablets
- ],
- [MODEL, [VENDOR, "RCA"], [TYPE, TABLET]],
- [
- /\b(venue[\d ]{2,7}) b/i // Dell Venue Tablets
- ],
- [MODEL, [VENDOR, "Dell"], [TYPE, TABLET]],
- [
- /\b(q(?:mv|ta)\w+) b/i // Verizon Tablet
- ],
- [MODEL, [VENDOR, "Verizon"], [TYPE, TABLET]],
- [
- /\b(?:barnes[& ]+noble |bn[rt])([\w\+ ]*) b/i // Barnes & Noble Tablet
- ],
- [MODEL, [VENDOR, "Barnes & Noble"], [TYPE, TABLET]],
- [/\b(tm\d{3}\w+) b/i],
- [MODEL, [VENDOR, "NuVision"], [TYPE, TABLET]],
- [
- /\b(k88) b/i // ZTE K Series Tablet
- ],
- [MODEL, [VENDOR, "ZTE"], [TYPE, TABLET]],
- [
- /\b(nx\d{3}j) b/i // ZTE Nubia
- ],
- [MODEL, [VENDOR, "ZTE"], [TYPE, MOBILE]],
- [
- /\b(gen\d{3}) b.+49h/i // Swiss GEN Mobile
- ],
- [MODEL, [VENDOR, "Swiss"], [TYPE, MOBILE]],
- [
- /\b(zur\d{3}) b/i // Swiss ZUR Tablet
- ],
- [MODEL, [VENDOR, "Swiss"], [TYPE, TABLET]],
- [
- /\b((zeki)?tb.*\b) b/i // Zeki Tablets
- ],
- [MODEL, [VENDOR, "Zeki"], [TYPE, TABLET]],
- [
- /\b([yr]\d{2}) b/i,
- /\b(dragon[- ]+touch |dt)(\w{5}) b/i // Dragon Touch Tablet
- ],
- [[VENDOR, "Dragon Touch"], MODEL, [TYPE, TABLET]],
- [
- /\b(ns-?\w{0,9}) b/i // Insignia Tablets
- ],
- [MODEL, [VENDOR, "Insignia"], [TYPE, TABLET]],
- [
- /\b((nxa|next)-?\w{0,9}) b/i // NextBook Tablets
- ],
- [MODEL, [VENDOR, "NextBook"], [TYPE, TABLET]],
- [
- /\b(xtreme\_)?(v(1[045]|2[015]|[3469]0|7[05])) b/i // Voice Xtreme Phones
- ],
- [[VENDOR, "Voice"], MODEL, [TYPE, MOBILE]],
- [
- /\b(lvtel\-)?(v1[12]) b/i // LvTel Phones
- ],
- [[VENDOR, "LvTel"], MODEL, [TYPE, MOBILE]],
- [
- /\b(ph-1) /i // Essential PH-1
- ],
- [MODEL, [VENDOR, "Essential"], [TYPE, MOBILE]],
- [
- /\b(v(100md|700na|7011|917g).*\b) b/i // Envizen Tablets
- ],
- [MODEL, [VENDOR, "Envizen"], [TYPE, TABLET]],
- [
- /\b(trio[-\w\. ]+) b/i // MachSpeed Tablets
- ],
- [MODEL, [VENDOR, "MachSpeed"], [TYPE, TABLET]],
- [
- /\btu_(1491) b/i // Rotor Tablets
- ],
- [MODEL, [VENDOR, "Rotor"], [TYPE, TABLET]],
- [
- /(shield[\w ]+) b/i // Nvidia Shield Tablets
- ],
- [MODEL, [VENDOR, "Nvidia"], [TYPE, TABLET]],
- [
- /(sprint) (\w+)/i // Sprint Phones
- ],
- [VENDOR, MODEL, [TYPE, MOBILE]],
- [
- /(kin\.[onetw]{3})/i // Microsoft Kin
- ],
- [
- [MODEL, /\./g, " "],
- [VENDOR, MICROSOFT],
- [TYPE, MOBILE]
- ],
- [
- /droid.+; (cc6666?|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i // Zebra
- ],
- [MODEL, [VENDOR, ZEBRA], [TYPE, TABLET]],
- [/droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i],
- [MODEL, [VENDOR, ZEBRA], [TYPE, MOBILE]],
- [
- ///////////////////
- // CONSOLES
- ///////////////////
-
- /(ouya)/i, // Ouya
- /(nintendo) ([wids3utch]+)/i // Nintendo
- ],
- [VENDOR, MODEL, [TYPE, CONSOLE]],
- [
- /droid.+; (shield) bui/i // Nvidia
- ],
- [MODEL, [VENDOR, "Nvidia"], [TYPE, CONSOLE]],
- [
- /(playstation [345portablevi]+)/i // Playstation
- ],
- [MODEL, [VENDOR, SONY], [TYPE, CONSOLE]],
- [
- /\b(xbox(?: one)?(?!; xbox))[\); ]/i // Microsoft Xbox
- ],
- [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]],
- [
- ///////////////////
- // SMARTTVS
- ///////////////////
-
- /smart-tv.+(samsung)/i // Samsung
- ],
- [VENDOR, [TYPE, SMARTTV]],
- [/hbbtv.+maple;(\d+)/i],
- [
- [MODEL, /^/, "SmartTV"],
- [VENDOR, SAMSUNG],
- [TYPE, SMARTTV]
- ],
- [
- /(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i // LG SmartTV
- ],
- [
- [VENDOR, LG],
- [TYPE, SMARTTV]
- ],
- [
- /(apple) ?tv/i // Apple TV
- ],
- [VENDOR, [MODEL, APPLE + " TV"], [TYPE, SMARTTV]],
- [
- /crkey/i // Google Chromecast
- ],
- [
- [MODEL, CHROME + "cast"],
- [VENDOR, GOOGLE],
- [TYPE, SMARTTV]
- ],
- [
- /droid.+aft(\w)( bui|\))/i // Fire TV
- ],
- [MODEL, [VENDOR, AMAZON], [TYPE, SMARTTV]],
- [
- /\(dtv[\);].+(aquos)/i,
- /(aquos-tv[\w ]+)\)/i // Sharp
- ],
- [MODEL, [VENDOR, SHARP], [TYPE, SMARTTV]],
- [
- /(bravia[\w ]+)( bui|\))/i // Sony
- ],
- [MODEL, [VENDOR, SONY], [TYPE, SMARTTV]],
- [
- /(mitv-\w{5}) bui/i // Xiaomi
- ],
- [MODEL, [VENDOR, XIAOMI], [TYPE, SMARTTV]],
- [
- /\b(roku)[\dx]*[\)\/]((?:dvp-)?[\d\.]*)/i, // Roku
- /hbbtv\/\d+\.\d+\.\d+ +\([\w ]*; *(\w[^;]*);([^;]*)/i // HbbTV devices
- ],
- [
- [VENDOR, trim],
- [MODEL, trim],
- [TYPE, SMARTTV]
- ],
- [
- /\b(android tv|smart[- ]?tv|opera tv|tv; rv:)\b/i // SmartTV from Unidentified Vendors
- ],
- [[TYPE, SMARTTV]],
- [
- ///////////////////
- // WEARABLES
- ///////////////////
-
- /((pebble))app/i // Pebble
- ],
- [VENDOR, MODEL, [TYPE, WEARABLE]],
- [
- /droid.+; (glass) \d/i // Google Glass
- ],
- [MODEL, [VENDOR, GOOGLE], [TYPE, WEARABLE]],
- [/droid.+; (wt63?0{2,3})\)/i],
- [MODEL, [VENDOR, ZEBRA], [TYPE, WEARABLE]],
- [
- /(quest( 2)?)/i // Oculus Quest
- ],
- [MODEL, [VENDOR, FACEBOOK], [TYPE, WEARABLE]],
- [
- ///////////////////
- // EMBEDDED
- ///////////////////
-
- /(tesla)(?: qtcarbrowser|\/[-\w\.]+)/i // Tesla
- ],
- [VENDOR, [TYPE, EMBEDDED]],
- [
- ////////////////////
- // MIXED (GENERIC)
- ///////////////////
-
- /droid .+?; ([^;]+?)(?: bui|\) applew).+? mobile safari/i // Android Phones from Unidentified Vendors
- ],
- [MODEL, [TYPE, MOBILE]],
- [
- /droid .+?; ([^;]+?)(?: bui|\) applew).+?(?! mobile) safari/i // Android Tablets from Unidentified Vendors
- ],
- [MODEL, [TYPE, TABLET]],
- [
- /\b((tablet|tab)[;\/]|focus\/\d(?!.+mobile))/i // Unidentifiable Tablet
- ],
- [[TYPE, TABLET]],
- [
- /(phone|mobile(?:[;\/]| [ \w\/\.]*safari)|pda(?=.+windows ce))/i // Unidentifiable Mobile
- ],
- [[TYPE, MOBILE]],
- [
- /(android[-\w\. ]{0,9});.+buil/i // Generic Android Device
- ],
- [MODEL, [VENDOR, "Generic"]]
- ],
-
- engine: [
- [
- /windows.+ edge\/([\w\.]+)/i // EdgeHTML
- ],
- [VERSION, [NAME, EDGE + "HTML"]],
- [
- /webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i // Blink
- ],
- [VERSION, [NAME, "Blink"]],
- [
- /(presto)\/([\w\.]+)/i, // Presto
- /(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna
- /ekioh(flow)\/([\w\.]+)/i, // Flow
- /(khtml|tasman|links)[\/ ]\(?([\w\.]+)/i, // KHTML/Tasman/Links
- /(icab)[\/ ]([23]\.[\d\.]+)/i // iCab
- ],
- [NAME, VERSION],
- [
- /rv\:([\w\.]{1,9})\b.+(gecko)/i // Gecko
- ],
- [VERSION, NAME]
- ],
-
- os: [
- [
- // Windows
- /microsoft (windows) (vista|xp)/i // Windows (iTunes)
- ],
- [NAME, VERSION],
- [
- /(windows) nt 6\.2; (arm)/i, // Windows RT
- /(windows (?:phone(?: os)?|mobile))[\/ ]?([\d\.\w ]*)/i, // Windows Phone
- /(windows)[\/ ]?([ntce\d\. ]+\w)(?!.+xbox)/i
- ],
- [NAME, [VERSION, strMapper, windowsVersionMap]],
- [/(win(?=3|9|n)|win 9x )([nt\d\.]+)/i],
- [
- [NAME, "Windows"],
- [VERSION, strMapper, windowsVersionMap]
- ],
- [
- // iOS/macOS
- /ip[honead]{2,4}\b(?:.*os ([\w]+) like mac|; opera)/i, // iOS
- /cfnetwork\/.+darwin/i
- ],
- [
- [VERSION, /_/g, "."],
- [NAME, "iOS"]
- ],
- [
- /(mac os x) ?([\w\. ]*)/i,
- /(macintosh|mac_powerpc\b)(?!.+haiku)/i // Mac OS
- ],
- [
- [NAME, "Mac OS"],
- [VERSION, /_/g, "."]
- ],
- [
- // Mobile OSes
- /droid ([\w\.]+)\b.+(android[- ]x86|harmonyos)/i // Android-x86/HarmonyOS
- ],
- [VERSION, NAME],
- [
- // Android/WebOS/QNX/Bada/RIM/Maemo/MeeGo/Sailfish OS
- /(android|webos|qnx|bada|rim tablet os|maemo|meego|sailfish)[-\/ ]?([\w\.]*)/i,
- /(blackberry)\w*\/([\w\.]*)/i, // Blackberry
- /(tizen|kaios)[\/ ]([\w\.]+)/i, // Tizen/KaiOS
- /\((series40);/i // Series 40
- ],
- [NAME, VERSION],
- [
- /\(bb(10);/i // BlackBerry 10
- ],
- [VERSION, [NAME, BLACKBERRY]],
- [
- /(?:symbian ?os|symbos|s60(?=;)|series60)[-\/ ]?([\w\.]*)/i // Symbian
- ],
- [VERSION, [NAME, "Symbian"]],
- [
- /mozilla\/[\d\.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w\.]+)/i // Firefox OS
- ],
- [VERSION, [NAME, FIREFOX + " OS"]],
- [
- /web0s;.+rt(tv)/i,
- /\b(?:hp)?wos(?:browser)?\/([\w\.]+)/i // WebOS
- ],
- [VERSION, [NAME, "webOS"]],
- [
- // Google Chromecast
- /crkey\/([\d\.]+)/i // Google Chromecast
- ],
- [VERSION, [NAME, CHROME + "cast"]],
- [
- /(cros) [\w]+ ([\w\.]+\w)/i // Chromium OS
- ],
- [[NAME, "Chromium OS"], VERSION],
- [
- // Console
- /(nintendo|playstation) ([wids345portablevuch]+)/i, // Nintendo/Playstation
- /(xbox); +xbox ([^\);]+)/i, // Microsoft Xbox (360, One, X, S, Series X, Series S)
-
- // Other
- /\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i, // Joli/Palm
- /(mint)[\/\(\) ]?(\w*)/i, // Mint
- /(mageia|vectorlinux)[; ]/i, // Mageia/VectorLinux
- /([kxln]?ubuntu|debian|suse|opensuse|gentoo|arch(?= linux)|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire)(?: gnu\/linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i,
- // Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware/Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus/Raspbian/Plan9/Minix/RISCOS/Contiki/Deepin/Manjaro/elementary/Sabayon/Linspire
- /(hurd|linux) ?([\w\.]*)/i, // Hurd/Linux
- /(gnu) ?([\w\.]*)/i, // GNU
- /\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i, // FreeBSD/NetBSD/OpenBSD/PC-BSD/GhostBSD/DragonFly
- /(haiku) (\w+)/i // Haiku
- ],
- [NAME, VERSION],
- [
- /(sunos) ?([\w\.\d]*)/i // Solaris
- ],
- [[NAME, "Solaris"], VERSION],
- [
- /((?:open)?solaris)[-\/ ]?([\w\.]*)/i, // Solaris
- /(aix) ((\d)(?=\.|\)| )[\w\.])*/i, // AIX
- /\b(beos|os\/2|amigaos|morphos|openvms|fuchsia|hp-ux)/i, // BeOS/OS2/AmigaOS/MorphOS/OpenVMS/Fuchsia/HP-UX
- /(unix) ?([\w\.]*)/i // UNIX
- ],
- [NAME, VERSION]
- ]
- };
-
- /////////////////
- // Constructor
- ////////////////
-
- var UAParser = function (ua, extensions) {
- if (typeof ua === OBJ_TYPE) {
- extensions = ua;
- ua = undefined;
- }
-
- if (!(this instanceof UAParser)) {
- return new UAParser(ua, extensions).getResult();
- }
-
- var _ua =
- ua ||
- (typeof window !== UNDEF_TYPE &&
- window.navigator &&
- window.navigator.userAgent ? window.navigator.userAgent : EMPTY);
- var _rgxmap = extensions ? extend(regexes, extensions) : regexes;
-
- this.getBrowser = function () {
- var _browser = {};
- _browser[NAME] = undefined;
- _browser[VERSION] = undefined;
- rgxMapper.call(_browser, _ua, _rgxmap.browser);
- _browser.major = majorize(_browser.version);
- return _browser;
- };
- this.getCPU = function () {
- var _cpu = {};
- _cpu[ARCHITECTURE] = undefined;
- rgxMapper.call(_cpu, _ua, _rgxmap.cpu);
- return _cpu;
- };
- this.getDevice = function () {
- var _device = {};
- _device[VENDOR] = undefined;
- _device[MODEL] = undefined;
- _device[TYPE] = undefined;
- rgxMapper.call(_device, _ua, _rgxmap.device);
- return _device;
- };
- this.getEngine = function () {
- var _engine = {};
- _engine[NAME] = undefined;
- _engine[VERSION] = undefined;
- rgxMapper.call(_engine, _ua, _rgxmap.engine);
- return _engine;
- };
- this.getOS = function () {
- var _os = {};
- _os[NAME] = undefined;
- _os[VERSION] = undefined;
- rgxMapper.call(_os, _ua, _rgxmap.os);
- return _os;
- };
- this.getResult = function () {
- return {
- ua: this.getUA(),
- browser: this.getBrowser(),
- engine: this.getEngine(),
- os: this.getOS(),
- device: this.getDevice(),
- cpu: this.getCPU()
- };
- };
- this.getUA = function () {
- return _ua;
- };
- this.setUA = function (ua) {
- _ua =
- typeof ua === STR_TYPE && ua.length > UA_MAX_LENGTH ? trim(ua, UA_MAX_LENGTH) : ua;
- return this;
- };
- this.setUA(_ua);
- return this;
- };
-
- UAParser.VERSION = LIBVERSION;
- UAParser.BROWSER = enumerize([NAME, VERSION, MAJOR]);
- UAParser.CPU = enumerize([ARCHITECTURE]);
- UAParser.DEVICE = enumerize([
- MODEL,
- VENDOR,
- TYPE,
- CONSOLE,
- MOBILE,
- SMARTTV,
- TABLET,
- WEARABLE,
- EMBEDDED
- ]);
- UAParser.ENGINE = UAParser.OS = enumerize([NAME, VERSION]);
-
- ///////////
- // Export
- //////////
-
- // check js environment
- if (typeof exports !== UNDEF_TYPE) {
- // nodejs env
- if (typeof module !== UNDEF_TYPE && module.exports) {
- exports = module.exports = UAParser;
- }
- exports.UAParser = UAParser;
- } else {
- // requirejs env (optional)
- if (typeof define === FUNC_TYPE && define.amd) {
- define(function () {
- return UAParser;
- });
- } else if (typeof window !== UNDEF_TYPE) {
- // browser env
- window.UAParser = UAParser;
- }
- }
-
- // jQuery/Zepto specific (optional)
- // Note:
- // In AMD env the global scope should be kept clean, but jQuery is an exception.
- // jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
- // and we should catch that.
- var $ = typeof window !== UNDEF_TYPE && (window.jQuery || window.Zepto);
- if ($ && !$.ua) {
- var parser = new UAParser();
- $.ua = parser.getResult();
- $.ua.get = function () {
- return parser.getUA();
- };
- $.ua.set = function (ua) {
- parser.setUA(ua);
- var result = parser.getResult();
- for (var prop in result) {
- $.ua[prop] = result[prop];
- }
- };
- }
-})(typeof window === "object" ? window : this);
diff --git a/test/browser-test.json b/test/data/ua/browser/browser-all.json
similarity index 57%
rename from test/browser-test.json
rename to test/data/ua/browser/browser-all.json
index 2be58c21b..a2a637903 100644
--- a/test/browser-test.json
+++ b/test/data/ua/browser/browser-all.json
@@ -1,12 +1,94 @@
[
+ {
+ "desc" : "115 Browser",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 115Browser/24.3.0.3",
+ "expect" :
+ {
+ "name" : "115",
+ "version" : "24.3.0.3",
+ "major" : "24"
+ }
+ },
+ {
+ "desc" : "2345 Browser",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.0; MI NOTE Pro Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Mobile Safari/537.36 Mb2345Browser/15.6.2",
+ "expect" :
+ {
+ "name" : "2345",
+ "version" : "15.6.2",
+ "major" : "15"
+ }
+ },
+ {
+ "desc" : "2345 Chrome",
+ "ua" : "Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML like Gecko) Chrome/39.0.2171.99 Safari/537.36 2345chrome v3.0.0.9739",
+ "expect" :
+ {
+ "name" : "2345",
+ "version" : "3.0.0.9739",
+ "major" : "3"
+ }
+ },
+ {
+ "desc" : "2345 Explorer",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.90 Safari/537.36 2345Explorer/9.2.1.17116",
+ "expect" :
+ {
+ "name" : "2345",
+ "version" : "9.2.1.17116",
+ "major" : "9"
+ }
+ },
{
"desc" : "360 Browser on iOS",
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4_1 like Mac OS X) AppleWebKit/607.3.9 (KHTML, like Gecko) Mobile/16G102 QHBrowser/317 QihooBrowser/4.0.10",
"expect" :
{
- "name" : "360 Browser",
- "version" : "undefined",
- "major" : "undefined"
+ "name" : "360",
+ "version" : "4.0.10",
+ "major" : "4"
+ }
+ },
+ {
+ "desc" : "360 Secure Browser on Windows 10",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 QIHU 360SE",
+ "expect" :
+ {
+ "name" : "360",
+ "version" : "86.0.4240.198",
+ "major" : "86"
+ }
+ },
+ {
+ "desc" : "360 Speed Browser on Windows 10",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 QIHU 360EE",
+ "expect" :
+ {
+ "name" : "360",
+ "version" : "86.0.4240.198",
+ "major" : "86"
+ }
+ },
+ {
+ "desc" : "Alipay",
+ "ua" : "Mozilla/5.0 (Linux; U; Android 10; zh-CN; V2034A Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 UWS/3.22.2.33 Mobile Safari/537.36 UCBS/3.22.2.33_211025173018 NebulaSDK/1.8.100112 Nebula AlipayDefined(nt:WIFI,ws:360|0|2.0) AliApp(AP/10.2.51.7100) AlipayClient/10.2.51.7100 Language/zh-Hans useStatusBar/true isConcaveScreen/true Region/CNAriver/1.0.0",
+ "expect" :
+ {
+ "name" : "Alipay",
+ "version" : "10.2.51.7100",
+ "major" : "10",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Alipay",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; VOG-AL00 Build/HUAWEIVOG-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/105.0.5195.148 MYWeb/0.2.103.0_20230131112530 UWS/3.22.2.9999 UCBS/3.22.2.9999_220000000000 Mobile Safari/537.36 NebulaSDK/1.8.100112 Nebula AlipayDefined(nt:WIFI,ws:360|0|3.0) AliApp(AP/10.3.50.9999) AlipayClient/10.3.50.9999 Language/en isConcaveScreen/true Region/CN ProductType/devAriver/1.0.0",
+ "expect" :
+ {
+ "name" : "Alipay",
+ "version" : "10.3.50.9999",
+ "major" : "10",
+ "type" : "inapp"
}
},
{
@@ -64,7 +146,7 @@
"ua" : "Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/3.0 Chrome/38.0.2125.102 Mobile Safari/537.36",
"expect" :
{
- "name" : "Samsung Browser",
+ "name" : "Samsung Internet",
"version" : "3.0",
"major" : "3"
}
@@ -94,7 +176,7 @@
"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)",
"expect" :
{
- "name" : "Avant ",
+ "name" : "Avant",
"version" : "undefined",
"major" : "undefined"
}
@@ -124,11 +206,81 @@
"ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; baidubrowser 1.x)",
"expect" :
{
- "name" : "baidubrowser",
+ "name" : "Baidu",
"version" : "1.x",
"major" : "1"
}
},
+ {
+ "desc" : "Baidu",
+ "ua" : "Mozilla/5.0 (Linux; Android 9; Redmi Note 5 Build/PKQ1.180904.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/110.0.5481.153 Mobile Safari/537.36 bdbrowser/6.4.0.4",
+ "expect" :
+ {
+ "name" : "Baidu",
+ "version" : "6.4.0.4",
+ "major" : "6"
+ }
+ },
+ {
+ "desc" : "Baidu",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.4.9999.1900 Safari/537.31 BDSpark/26.4",
+ "expect" :
+ {
+ "name" : "Baidu",
+ "version" : "26.4",
+ "major" : "26"
+ }
+ },
+ {
+ "desc" : "Baidu",
+ "ua" : "Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) BaiduHD/5.4.0.0 Mobile/10A406 Safari/8536.25",
+ "expect" :
+ {
+ "name" : "Baidu",
+ "version" : "5.4.0.0",
+ "major" : "5"
+ }
+ },
+ {
+ "desc" : "BaiDu Browser",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 BIDUBrowser/8.7 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Baidu",
+ "version" : "8.7",
+ "major" : "8"
+ }
+ },
+ {
+ "desc" : "baidu app on iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C101 main%2F1.0 baiduboxapp/11.12.0.18 (Baidu; P2 12.1.2)",
+ "expect" :
+ {
+ "name" : "Baidu",
+ "version" : "11.12.0.18",
+ "major" : "11"
+ }
+ },
+ {
+ "desc" : "baidu app on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 8.1.0; BKK-AL10 Build/HONORBKK-AL10; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.83 Mobile Safari/537.36 T7/11.11 baiduboxapp/11.11.0.0 (Baidu; P1 8.1.0)",
+ "expect" :
+ {
+ "name" : "Baidu",
+ "version" : "11.11.0.0",
+ "major" : "11"
+ }
+ },
+ {
+ "desc" : "Blazer",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; PalmSource/hspr-H102; Blazer/4.0) 16;320x320",
+ "expect" :
+ {
+ "name" : "Blazer",
+ "version" : "4.0",
+ "major" : "4"
+ }
+ },
{
"desc" : "Bolt",
"ua" : "Mozilla/5.0 (X11; 78; CentOS; US-en) AppleWebKit/527+ (KHTML, like Gecko) Bolt/0.862 Version/3.0 Safari/523.15",
@@ -199,6 +351,46 @@
"major" : "100"
}
},
+ {
+ "desc" : "Chrome 112.0 on Win10",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Chrome",
+ "version" : "112.0.0.0",
+ "major" : "112"
+ }
+ },
+ {
+ "desc" : "Chrome 112.0 on macOS",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Chrome",
+ "version" : "112.0.0.0",
+ "major" : "112"
+ }
+ },
+ {
+ "desc" : "Chrome 111.0 on Linux",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Chrome",
+ "version" : "111.0.0.0",
+ "major" : "111"
+ }
+ },
+ {
+ "desc" : "Chrome 111.0 on ChromeOS",
+ "ua" : "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Chrome",
+ "version" : "111.0.0.0",
+ "major" : "111"
+ }
+ },
{
"desc" : "Chrome Headless",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome Safari/537.36",
@@ -234,7 +426,7 @@
"ua" : "Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3",
"expect" :
{
- "name" : "Chrome",
+ "name" : "Mobile Chrome",
"version" : "19.0.1084.60",
"major" : "19"
}
@@ -254,7 +446,7 @@
"ua" : "Mozilla/5.0 (Linux; U; Android-4.0.3; en-us; Galaxy Nexus Build/IML74K) AppleWebKit/535.7 (KHTML, like Gecko) CrMo/16.0.912.75 Mobile Safari/535.7",
"expect" :
{
- "name" : "Chrome",
+ "name" : "Mobile Chrome",
"version" : "16.0.912.75",
"major" : "16"
}
@@ -269,6 +461,36 @@
"major" : "78"
}
},
+ {
+ "desc" : "Comodo Dragon",
+ "ua" : "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/535.7 (KHTML, like Gecko) Comodo_Dragon/16.1.1.0 Chrome/16.0.912.63 Safari/535.7",
+ "expect" :
+ {
+ "name" : "Dragon",
+ "version" : "16.1.1.0",
+ "major" : "16"
+ }
+ },
+ {
+ "desc" : "Comodo Dragon",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Dragon/98.0.4758.102 Chrome/98.0.4758.102 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Dragon",
+ "version" : "98.0.4758.102",
+ "major" : "98"
+ }
+ },
+ {
+ "desc" : "Conkeror",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; rv:6.0.1) Gecko/20110831 conkeror/0.9.3",
+ "expect" :
+ {
+ "name" : "conkeror",
+ "version" : "0.9.3",
+ "major" : "0"
+ }
+ },
{
"desc" : "Dillo",
"ua" : "Dillo/2.2",
@@ -289,6 +511,16 @@
"major" : "2"
}
},
+ {
+ "desc" : "Dooble",
+ "ua" : "Mozilla/5.0 (X11; Haiku BePC) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.15.17 Chrome/87.0.4280.144 Safari/537.36 Dooble/2023.12.25 Dooble/2023.12.25",
+ "expect" :
+ {
+ "name" : "Dooble",
+ "version" : "2023.12.25",
+ "major" : "2023"
+ }
+ },
{
"desc" : "Doris",
"ua" : "Doris/1.15 [en] (Symbian)",
@@ -299,6 +531,16 @@
"major" : "1"
}
},
+ {
+ "desc" : "DuckDuckGo",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.1517.4.1 Ddg/17.4.1",
+ "expect" :
+ {
+ "name" : "DuckDuckGo",
+ "version" : "17.4.1",
+ "major" : "17"
+ }
+ },
{
"desc" : "DuckDuckGo",
"ua" : "Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.131 Mobile DuckDuckGo/5 Safari/537.36",
@@ -309,6 +551,26 @@
"major" : "5"
}
},
+ {
+ "desc" : "Ecosia on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; SM-G975U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36 (Ecosia android@85.0.4183.127)",
+ "expect" :
+ {
+ "name" : "Ecosia",
+ "version" : "85.0.4183.127",
+ "major" : "85"
+ }
+ },
+ {
+ "desc" : "Ecosia on iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like Mac OS X) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60 (Ecosia ios@3.0.1.533)",
+ "expect" :
+ {
+ "name" : "Ecosia",
+ "version" : "3.0.1.533",
+ "major" : "3"
+ }
+ },
{
"desc" : "Epiphany",
"ua" : "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.7) Gecko/20040628 Epiphany/1.2.6",
@@ -329,6 +591,16 @@
"major" : "5"
}
},
+ {
+ "desc" : "Go Browser",
+ "ua" : "NokiaE66/GoBrowser/2.0.297",
+ "expect" :
+ {
+ "name" : "GoBrowser",
+ "version" : "2.0.297",
+ "major" : "2"
+ }
+ },
{
"desc" : "Waterfox",
"ua" : "Mozilla/5.0 (X11; Linux x86_64; rv:55.0) Gecko/20100101 Firefox/55.2.2 Waterfox/55.2.2",
@@ -366,7 +638,8 @@
{
"name" : "Facebook",
"version" : "35.0.0.48.273",
- "major" : "35"
+ "major" : "35",
+ "type" : "inapp"
}
},
{
@@ -376,7 +649,8 @@
{
"name" : "Facebook",
"version" : "91.0.0.41.73",
- "major" : "91"
+ "major" : "91",
+ "type" : "inapp"
}
},
{
@@ -386,7 +660,30 @@
{
"name" : "Facebook",
"version" : "undefined",
- "major" : "undefined"
+ "major" : "undefined",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Klarna in-App Browser for iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Klarna/23.36.223",
+ "expect" :
+ {
+ "name" : "Klarna",
+ "version" : "23.36.223",
+ "major" : "23",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Klarna in-App Browser for Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 12; moto g(60)s Build/S3RLS32.114-25-13; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 Klarna/23.36.215",
+ "expect" :
+ {
+ "name" : "Klarna",
+ "version" : "23.36.215",
+ "major" : "23",
+ "type" : "inapp"
}
},
{
@@ -396,7 +693,8 @@
{
"name" : "Instagram",
"version" : "142.0.0.22.109",
- "major" : "142"
+ "major" : "142",
+ "type" : "inapp"
}
},
{
@@ -508,6 +806,26 @@
"major" : "1"
}
},
+ {
+ "desc" : "Helio",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36 Helio/0.98.20",
+ "expect" :
+ {
+ "name" : "Helio",
+ "version" : "0.98.20",
+ "major" : "0"
+ }
+ },
+ {
+ "desc" : "HeyTap",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.61 Safari/537.36 HeyTapBrowser/40.8.10.1",
+ "expect" :
+ {
+ "name" : "HeyTap",
+ "version" : "40.8.10.1",
+ "major" : "40"
+ }
+ },
{
"desc" : "HuaweiBrowser",
"ua" : "Mozilla/5.0 (Linux; Android 6.0.1; LYA-AL00īŧHMSCore/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",
@@ -558,6 +876,16 @@
"major" : "2"
}
},
+ {
+ "desc" : "ICEBrowser",
+ "ua" : "Mozilla/5.0 (Java 1.6.0_01; Windows XP 5.1 x86; en) ICEbrowser/v6_1_2",
+ "expect" :
+ {
+ "name" : "ICEbrowser",
+ "version" : "6.1.2",
+ "major" : "6"
+ }
+ },
{
"desc" : "IceCat",
"ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092921 IceCat/3.0.3-g1",
@@ -618,6 +946,46 @@
"major" : "11"
}
},
+ {
+ "desc" : "Iron",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1250.0 Iron/22.0.2150.0 Safari/537.4",
+ "expect" :
+ {
+ "name" : "Iron",
+ "version" : "22.0.2150.0",
+ "major" : "22"
+ }
+ },
+ {
+ "desc" : "Iron",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Iron Safari/537.36",
+ "expect" :
+ {
+ "name" : "Iron",
+ "version" : "129.0.0.0",
+ "major" : "129"
+ }
+ },
+ {
+ "desc" : "Iron",
+ "ua" : "Mozilla/5.0 (Linux; Android 11; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Iron Safari/537.36",
+ "expect" :
+ {
+ "name" : "Iron",
+ "version" : "113.0.0.0",
+ "major" : "113"
+ }
+ },
+ {
+ "desc" : "Jasmine",
+ "ua" : "SAMSUNG-S8000/S8000XXIF3 SHP/VPP/R5 Jasmine/1.0 Nextreaming SMM-MMS/1.2.0 profile/MIDP-2.1 configuration/CLDC-1.1",
+ "expect" :
+ {
+ "name" : "Jasmine",
+ "version" : "1.0",
+ "major" : "1"
+ }
+ },
{
"desc" : "K-Meleon",
"ua" : "Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.5) Gecko/20031016 K-Meleon/0.8.2",
@@ -638,6 +1006,16 @@
"major" : "2"
}
},
+ {
+ "desc" : "Klar < 4.1",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Klar/1.0 Chrome/58.0.3029.83 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Klar",
+ "version" : "1.0",
+ "major" : "1"
+ }
+ },
{
"desc" : "Konqueror",
"ua" : "Mozilla/5.0 (compatible; Konqueror/3.5; Linux; X11; x86_64) KHTML/3.5.6 (like Gecko) (Kubuntu)",
@@ -659,41 +1037,153 @@
}
},
{
- "desc" : "LINE on Android",
- "ua" : "Mozilla/5.0 (Linux; Android 5.0; ASUS_Z00AD Build/LRX21V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36 Line/6.5.1/IAB",
+ "desc" : "Otter",
+ "ua" : "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/602.1 (KHTML, like Gecko) Otter/1.0.81",
"expect" :
{
- "name" : "Line",
- "version" : "6.5.1",
- "major" : "6"
+ "name" : "Otter",
+ "version" : "1.0.81",
+ "major" : "1"
}
},
{
- "desc" : "LINE on iOS",
- "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Mobile/15D100 Safari Line/8.4.1",
+ "desc" : "PicoBrowser",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; Pico Neo3 Link OS5.8.4.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.22 Chrome/105.0.5195.68 VR Safari/537.36",
"expect" :
{
- "name" : "Line",
- "version" : "8.4.1",
- "major" : "8"
+ "name" : "Pico Browser",
+ "version" : "3.3.22",
+ "major" : "3"
}
},
{
- "desc" : "Lunascape",
- "ua" : "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.2) Gecko/20090804 Firefox/3.5.2 Lunascape/5.1.4.5",
+ "desc" : "PicoBrowser",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; PICO 4 OS5.8.2 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.38 Chrome/105.0.5195.68 VR Safari/537.36",
"expect" :
{
- "name" : "Lunascape",
- "version" : "5.1.4.5",
- "major" : "5"
+ "name" : "Pico Browser",
+ "version" : "3.3.38",
+ "major" : "3"
}
},
{
- "desc" : "Lynx",
- "ua" : "Lynx/2.8.5dev.16 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6b",
+ "desc" : "PicoBrowser",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; PICO 4 OS5.4.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.22 Chrome/105.0.5195.68 VR Safari/537.36 OculusBrowser/7.0",
"expect" :
{
- "name" : "Lynx",
+ "name" : "Pico Browser",
+ "version" : "3.3.22",
+ "major" : "3"
+ }
+ },
+ {
+ "desc" : "Rekonq",
+ "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ) AppleWebKit/533.3 (KHTML, like Gecko) rekonq Safari/533.3",
+ "expect" :
+ {
+ "name" : "rekonq",
+ "version" : "undefined",
+ "major" : "undefined"
+ }
+ },
+ {
+ "desc" : "Smart Lenovo Browser",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 SLBrowser/8.0.0.10171 SLBChan/8",
+ "expect" :
+ {
+ "name" : "Smart Lenovo Browser",
+ "version" : "8.0.0.10171",
+ "major" : "8"
+ }
+ },
+ {
+ "desc" : "Smart Lenovo Browser",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 SLBrowser/9.0.0.9011 SLBChan/10",
+ "expect" :
+ {
+ "name" : "Smart Lenovo Browser",
+ "version" : "9.0.0.9011",
+ "major" : "9"
+ }
+ },
+ {
+ "desc" : "Surf Browser",
+ "ua" : "Mozilla/5.0 (Android 6.0; HUAWEI ALE-L21) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36 SurfBrowser/3.0",
+ "expect" :
+ {
+ "name" : "Surf",
+ "version" : "3.0",
+ "major" : "3"
+ }
+ },
+ {
+ "desc" : "LG Browser",
+ "ua" : "Mozilla/5.0 (Unknown; Linux armv7l) AppleWebKit/537.1+ (KHTML, like Gecko) Safari/537.1+ LG Browser/6.00.00(+mouse+3D+SCREEN+TUNER; LGE; 47LA621V-ZD; 04.28.17; 0x00000001;); LG NetCast.TV-2013 /04.28.17 (LG, 47LA621V-ZD, wired)",
+ "expect" :
+ {
+ "name" : "LG Browser",
+ "version" : "6.00.00",
+ "major" : "6"
+ }
+ },
+ {
+ "desc" : "Ladybird",
+ "ua" : "Mozilla/5.0 (Linux; x86_64) Ladybird/1.0",
+ "expect" :
+ {
+ "name" : "Ladybird",
+ "version" : "1.0",
+ "major" : "1"
+ }
+ },
+ {
+ "desc" : "LibreWolf",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0 LibreWolf/97.0.1",
+ "expect" :
+ {
+ "name" : "LibreWolf",
+ "version" : "97.0.1",
+ "major" : "97"
+ }
+ },
+ {
+ "desc" : "LINE on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 5.0; ASUS_Z00AD Build/LRX21V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36 Line/6.5.1/IAB",
+ "expect" :
+ {
+ "name" : "Line",
+ "version" : "6.5.1",
+ "major" : "6",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "LINE on iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Mobile/15D100 Safari Line/8.4.1",
+ "expect" :
+ {
+ "name" : "Line",
+ "version" : "8.4.1",
+ "major" : "8",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Lunascape",
+ "ua" : "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.2) Gecko/20090804 Firefox/3.5.2 Lunascape/5.1.4.5",
+ "expect" :
+ {
+ "name" : "Lunascape",
+ "version" : "5.1.4.5",
+ "major" : "5"
+ }
+ },
+ {
+ "desc" : "Lynx",
+ "ua" : "Lynx/2.8.5dev.16 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6b",
+ "expect" :
+ {
+ "name" : "Lynx",
"version" : "2.8.5dev.16",
"major" : "2"
}
@@ -709,7 +1199,57 @@
}
},
{
- "desc" : "Maxthon",
+ "desc" : "Maxthon on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 5.1.1; KFAUWI Build/LVY48F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.127 Safari/537.36 MxBrowser/4.3.5.2000",
+ "expect" :
+ {
+ "name" : "Maxthon",
+ "version" : "4.3.5.2000",
+ "major" : "4"
+ }
+ },
+ {
+ "desc" : "Maxthon on iOS",
+ "ua" : "Mozilla/5.0 (iPad; CPU OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1 MXiOS/5.4.5.2",
+ "expect" :
+ {
+ "name" : "Maxthon",
+ "version" : "5.4.5.2",
+ "major" : "5"
+ }
+ },
+ {
+ "desc" : "Maxthon on Linux",
+ "ua" : "Mozilla/5.0 (X11; Linux i686; Ubuntu 14.04.3 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Maxthon",
+ "version" : "1.0.5.3",
+ "major" : "1"
+ }
+ },
+ {
+ "desc" : "Maxthon on macOS",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6 Maxthon/5.1.102",
+ "expect" :
+ {
+ "name" : "Maxthon",
+ "version" : "5.1.102",
+ "major" : "5"
+ }
+ },
+ {
+ "desc" : "Maxthon on Windows Server 2003",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; MyIE2; .NET CLR 1.1.4322)",
+ "expect" :
+ {
+ "name" : "Maxthon",
+ "version" : "undefined",
+ "major" : "undefined"
+ }
+ },
+ {
+ "desc" : "Maxthon on Windows XP",
"ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; Maxthon; .NET CLR 1.1.4322)",
"expect" :
{
@@ -718,6 +1258,16 @@
"major" : "undefined"
}
},
+ {
+ "desc" : "Maxthon on Windows 10",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36 Maxthon/5.2.7.2000",
+ "expect" :
+ {
+ "name" : "Maxthon",
+ "version" : "5.2.7.2000",
+ "major" : "5"
+ }
+ },
{
"desc" : "Midori",
"ua" : "Midori/0.2.2 (X11; Linux i686; U; en-us) WebKit/531.2+",
@@ -758,6 +1308,16 @@
"major" : "4"
}
},
+ {
+ "desc" : "Mobile Safari",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Safari/604.1",
+ "expect" :
+ {
+ "name" : "Mobile Safari",
+ "version" : "undefined",
+ "major" : "undefined"
+ }
+ },
{
"desc" : "Mosaic",
"ua" : "NCSA_Mosaic/2.6 (X11; SunOS 4.1.3 sun4m)",
@@ -828,6 +1388,26 @@
"major" : "6"
}
},
+ {
+ "desc" : "NetSurf in Plan9",
+ "ua" : "Mozilla/5.0 (Plan9) NetSurf/3.12",
+ "expect" :
+ {
+ "name" : "NetSurf",
+ "version" : "3.12",
+ "major" : "3"
+ }
+ },
+ {
+ "desc" : "NetSurf in Linux",
+ "ua" : "NetSurf/3.10 (Linux; Arch Linux)",
+ "expect" :
+ {
+ "name" : "NetSurf",
+ "version" : "3.10",
+ "major" : "3"
+ }
+ },
{
"desc" : "Nokia Browser",
"ua" : "Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaN8-00/025.007; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.1.37 Mobile Safari/533.4 3gpp-gba",
@@ -958,6 +1538,26 @@
"major" : "12"
}
},
+ {
+ "desc" : "Opera GX on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; Redmi Note 8 Pro Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.168 Mobile Safari/537.36 OPX/2",
+ "expect" :
+ {
+ "name" : "Opera GX",
+ "version" : "2",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "Opera GX on Windows",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36 OPR/60.0.3255.50747 OPRGX/60.0.3255.50747",
+ "expect" :
+ {
+ "name" : "Opera GX",
+ "version" : "60.0.3255.50747",
+ "major" : "60"
+ }
+ },
{
"desc" : "Opera Tablet",
"ua" : "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1",
@@ -988,6 +1588,16 @@
"major" : "1"
}
},
+ {
+ "desc" : "OviBrowser",
+ "ua" : "Mozilla/5.0 (Series40; NokiaX3-02/le6.32; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/1.0.0.11.8",
+ "expect" :
+ {
+ "name" : "OviBrowser",
+ "version" : "1.0.0.11.8",
+ "major" : "1"
+ }
+ },
{
"desc" : "PhantomJS",
"ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.9.2 Safari/534.34",
@@ -1019,7 +1629,7 @@
}
},
{
- "desc" : "QQ",
+ "desc" : "QQBrowser",
"ua" : "Mozilla/5.0 (Linux; U; Android 4.4.4; zh-cn; OPPO R7s Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 Chrome/37.0.0.0 MQQBrowser/7.1 Mobile Safari/537.36",
"expect" :
{
@@ -1028,6 +1638,36 @@
"major" : "7"
}
},
+ {
+ "desc" : "QQBrowser",
+ "ua" : "Mozilla/5.0 (Linux; U; Android 9; zh-cn; vivo X21 Build/PKQ1.180819.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.9 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "QQBrowser",
+ "version" : "9.9",
+ "major" : "9"
+ }
+ },
+ {
+ "desc" : "Quark",
+ "ua" : "Mozilla/5.0 (Linux; U; Android 12; zh-Hans-CN; JLH-AN00 Build/HONORJLH-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Quark/5.8.2.221 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Quark",
+ "version" : "5.8.2.221",
+ "major" : "5"
+ }
+ },
+ {
+ "desc" : "Quark",
+ "ua" : "mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/112.0.0.0 safari/537.36 quarkpc/1.5.5.75",
+ "expect" :
+ {
+ "name" : "Quark",
+ "version" : "1.5.5.75",
+ "major" : "1"
+ }
+ },
{
"desc" : "QupZilla",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) QupZilla/1.8.9 Safari/538.1",
@@ -1038,6 +1678,26 @@
"major" : "1"
}
},
+ {
+ "desc" : "qutebrowser",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) qutebrowser/2.4.0 QtWebEngine/5.15.6 Chrome/95.0.4628.2 Safari/537.36",
+ "expect" :
+ {
+ "name" : "qutebrowser",
+ "version" : "2.4.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",
+ "expect" :
+ {
+ "name" : "rekonq",
+ "version" : "2.2.1",
+ "major" : "2"
+ }
+ },
{
"desc" : "RockMelt",
"ua" : "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) RockMelt/0.8.36.78 Chrome/7.0.517.44 Safari/534.7",
@@ -1064,20 +1724,60 @@
"expect" :
{
"name" : "Safari",
- "version" : "2.0.4",
- "major" : "2"
+ "version" : "1",
+ "major" : "1"
}
},
{
- "desc" : "Samsung Browser",
+ "desc" : "Samsung Internet for Android",
"ua" : "Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG-SM-G925A Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36",
"expect" :
{
- "name" : "Samsung Browser",
+ "name" : "Samsung Internet",
+ "version" : "4.0",
+ "major" : "4"
+ }
+ },
+ {
+ "desc" : "Samsung Internet for Tizen Mobile",
+ "ua" : "Mozilla/5.0 (Linux; Tizen 2.3; SAMSUNG SM-Z130H) AppleWebKit/537.3 (KHTML, like Gecko) SamsungBrowser/1.0 Mobile Safari/537.3",
+ "expect" :
+ {
+ "name" : "Samsung Internet",
+ "version" : "1.0",
+ "major" : "1"
+ }
+ },
+ {
+ "desc" : "Samsung Internet for Smart-TV",
+ "ua" : "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1",
+ "expect" :
+ {
+ "name" : "Samsung Internet",
+ "version" : "1.0",
+ "major" : "1"
+ }
+ },
+ {
+ "desc" : "Samsung Internet for Gear VR",
+ "ua" : "Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-G925K Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile VR Safari/537.36",
+ "expect" :
+ {
+ "name" : "Samsung Internet",
"version" : "4.0",
"major" : "4"
}
},
+ {
+ "desc" : "Samsung Internet in Redmi 8A",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; Redmi 8A) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Samsung Internet",
+ "version" : "23.0",
+ "major" : "23"
+ }
+ },
{
"desc" : "SeaMonkey",
"ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1b4pre) Gecko/20090405 SeaMonkey/2.0b1pre",
@@ -1117,17 +1817,68 @@
"version" : "2.0",
"major" : "2"
}
+ },
+ {
+ "desc" : "Sleipnir",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; SOV37 Build/52.1.C.0.220; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/123.0.6312.120 Mobile Safari/537.36 Sleipnir/3.7.5",
+ "expect" :
+ {
+ "name" : "Sleipnir",
+ "version" : "3.7.5",
+ "major" : "3"
+ }
+ },
+
+ {
+ "desc" : "Sleipnir",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Sleipnir 2.8.4)",
+ "expect" :
+ {
+ "name" : "Sleipnir",
+ "version" : "2.8.4",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "Sleipnir",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.1; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022) Sleipnir/2.8.4",
+ "expect" :
+ {
+ "name" : "Sleipnir",
+ "version" : "2.8.4",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "SlimBoat",
+ "ua" : "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.34 (KHTML, like Gecko) SlimBoat/1.1.23 Chrome/11.0.696.7 Version/5.1 Safari/534.34",
+ "expect" :
+ {
+ "name" : "SlimBoat",
+ "version" : "1.1.23",
+ "major" : "1"
+ }
},
{
"desc" : "SlimBrowser",
"ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SlimBrowser)",
"expect" :
{
- "name" : "Slim",
+ "name" : "SlimBrowser",
"version" : "undefined",
"major" : "undefined"
}
},
+ {
+ "desc" : "Slimjet",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36 Slimjet/20.0.2.0",
+ "expect" :
+ {
+ "name" : "Slimjet",
+ "version" : "20.0.2.0",
+ "major" : "20"
+ }
+ },
{
"desc" : "Swiftfox",
"ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 (Swiftfox)",
@@ -1229,7 +1980,7 @@
}
},
{
- "desc" : "UPBrowser",
+ "desc" : "UP.Browser",
"ua" : "BenQ-CF61/1.00/WAP2.0/MIDP2.0/CLDC1.0 UP.Browser/6.3.0.4.c.1.102 (GUI) MMP/2.0",
"expect" :
{
@@ -1278,6 +2029,46 @@
"major" : "1"
}
},
+ {
+ "desc" : "Vivaldi on Mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.88 Safari/537.36 Vivaldi/2.4.1488.36",
+ "expect" :
+ {
+ "name" : "Vivaldi",
+ "version" : "2.4.1488.36",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "Vivo Browser",
+ "ua" : "Mozilla/5.0 (Linux; Android 13; 23049RAD8C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/16.7.1.1",
+ "expect" :
+ {
+ "name" : "Vivo Browser",
+ "version" : "16.7.1.1",
+ "major" : "16"
+ }
+ },
+ {
+ "desc" : "w3m",
+ "ua" : "w3m/0.5.1",
+ "expect" :
+ {
+ "name" : "w3m",
+ "version" : "0.5.1",
+ "major" : "0"
+ }
+ },
+ {
+ "desc" : "Wolvic",
+ "ua" : "Mozilla/5.0 (Android 12; Mobile VR; rv:121.0) Gecko/121.0 Firefox/121.0 Wolvic/1.6.1",
+ "expect" :
+ {
+ "name" : "Wolvic",
+ "version" : "1.6.1",
+ "major" : "1"
+ }
+ },
{
"desc" : "Yandex",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/536.5 (KHTML, like Gecko) YaBrowser/1.0.1084.5402 Chrome/19.0.1084.5402 Safari/536.5",
@@ -1288,6 +2079,66 @@
"major" : "1"
}
},
+ {
+ "desc" : "Yandex",
+ "ua" : "Mozilla/5.0 (Linux; arm_64; Android 11; M2101K7AG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.125 YaApp_Android/22.70 YaSearchBrowser/22.70 BroPP/1.0 SA/3 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Yandex",
+ "version" : "22.70",
+ "major" : "22"
+ }
+ },
+ {
+ "desc" : "Yandex",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 YaBrowser/23.3.0.2246 Yowser/2.5 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Yandex",
+ "version" : "23.3.0.2246",
+ "major" : "23"
+ }
+ },
+ {
+ "desc" : "Yandex on Android",
+ "ua" : "Mozilla/5.0 (Linux; arm_64; Android 13; SM-G965F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.76 YaBrowser/21.3.4.59 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Yandex",
+ "version" : "21.3.4.59",
+ "major" : "21"
+ }
+ },
+ {
+ "desc" : "Yandex on iPhone",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 YaBrowser/23.3.3.330 Mobile/15E148 Safari/604.1",
+ "expect" :
+ {
+ "name" : "Yandex",
+ "version" : "23.3.3.330",
+ "major" : "23"
+ }
+ },
+ {
+ "desc" : "Yandex on iPad",
+ "ua" : "Mozilla/5.0 (iPad; CPU OS 16_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 YaBrowser/23.3.3.330 Mobile/15E148 Safari/605.1",
+ "expect" :
+ {
+ "name" : "Yandex",
+ "version" : "23.3.3.330",
+ "major" : "23"
+ }
+ },
+ {
+ "desc" : "Yandex on iPod",
+ "ua" : "Mozilla/5.0 (iPod touch; CPU iPhone 16_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 YaBrowser/23.3.3.330 Mobile/15E148 Safari/605.1",
+ "expect" :
+ {
+ "name" : "Yandex",
+ "version" : "23.3.3.330",
+ "major" : "23"
+ }
+ },
{
"desc" : "Puffin",
"ua" : "Mozilla/5.0 (Linux; Android 6.0.1; Lenovo P2a42 Build/MMB29M; en-us) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Puffin/6.0.8.15804AP",
@@ -1298,6 +2149,16 @@
"major" : "6"
}
},
+ {
+ "desc" : "Puffin",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.1.1; ZTE BLADE A0620 Build/NMF26F; ru-ru) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36 Puffin/9.2.0.50586AP",
+ "expect" :
+ {
+ "name" : "Puffin",
+ "version" : "9.2.0.50586AP",
+ "major" : "9"
+ }
+ },
{
"desc" : "Microsoft Edge 0.1",
"ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0",
@@ -1368,6 +2229,26 @@
"major" : "74"
}
},
+ {
+ "desc" : "Microsoft Edge WebView",
+ "ua" : "Mozilla/5.0 (Windows IoT 10.0; Android 6.0.1; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Mobile Safari/537.36 Edge/18.17763",
+ "expect" :
+ {
+ "name" : "Edge WebView",
+ "version" : "18.17763",
+ "major" : "18"
+ }
+ },
+ {
+ "desc" : "Microsoft Edge WebView2",
+ "ua" : "Mozilla/5.0 (Linux; Android 11; SM-G991B Build/RP1A.200720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.120 Mobile Safari/537.36 EdgW/1.0",
+ "expect" :
+ {
+ "name" : "Edge WebView2",
+ "version" : "91.0.4472.120",
+ "major" : "91"
+ }
+ },
{
"desc" : "Iridium",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Iridium/43.8 Safari/537.36 Chrome/43.0.2357.132",
@@ -1383,17 +2264,27 @@
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) FxiOS/1.1 Mobile/13B143 Safari/601.1.46",
"expect" :
{
- "name" : "Firefox",
+ "name" : "Mobile Firefox",
"version" : "1.1",
"major" : "1"
}
},
+ {
+ "desc" : "Firefox on iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/112.0 Mobile/15E148 Safari/605.1.15",
+ "expect" :
+ {
+ "name" : "Mobile Firefox",
+ "version" : "112.0",
+ "major" : "112"
+ }
+ },
{
"desc" : "Firefox iOS using iPad",
"ua" : "Mozilla/5.0 (iPad; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4",
"expect" :
{
- "name" : "Firefox",
+ "name" : "Mobile Firefox",
"version" : "1.0",
"major" : "1"
}
@@ -1403,7 +2294,7 @@
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Mobile/14A456 QQ/6.5.3.410 V1_IPH_SQ_6.5.3_1_APP_A Pixel/1080 Core/UIWebView NetType/WIFI Mem/26",
"expect" :
{
- "name" : "QQ",
+ "name" : "QQBrowser",
"version" : "6.5.3.410",
"major" : "6"
}
@@ -1413,37 +2304,17 @@
"ua" : "Mozilla/5.0 (Linux; Android 6.0; PRO 6 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile MQQBrowser/6.8 TBS/036824 Safari/537.36 V1_AND_SQ_6.5.8_422_YYB_D PA QQ/6.5.8.2910 NetType/WIFI WebP/0.3.0 Pixel/1080",
"expect" :
{
- "name" : "QQ",
+ "name" : "QQBrowser",
"version" : "6.5.8.2910",
"major" : "6"
}
},
- {
- "desc" : "baidu app on iOS",
- "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16C101 main%2F1.0 baiduboxapp/11.12.0.18 (Baidu; P2 12.1.2)",
- "expect" :
- {
- "name" : "baiduboxapp",
- "version" : "11.12.0.18",
- "major" : "11"
- }
- },
- {
- "desc" : "baidu app on Android",
- "ua" : "Mozilla/5.0 (Linux; Android 8.1.0; BKK-AL10 Build/HONORBKK-AL10; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.83 Mobile Safari/537.36 T7/11.11 baiduboxapp/11.11.0.0 (Baidu; P1 8.1.0)",
- "expect" :
- {
- "name" : "baiduboxapp",
- "version" : "11.11.0.0",
- "major" : "11"
- }
- },
{
"desc" : "WeChat Desktop for Windows Built-in Browser",
"ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.901.400 QQBrowser/9.0.2524.400",
"expect" :
{
- "name" : "WeChat(Win) Desktop",
+ "name" : "WeChat",
"version" : "3.43.901.400",
"major" : "3"
}
@@ -1453,7 +2324,7 @@
"ua" : "mozilla/5.0 (windows nt 10.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/53.0.2785.116 safari/537.36 qbcore/4.0.1301.400 qqbrowser/9.0.2524.400 mozilla/5.0 (windows nt 6.1; wow64) applewebkit/537.36 (khtml, like gecko) chrome/81.0.4044.138 safari/537.36 nettype/wifi micromessenger/7.0.20.1781(0x6700143b) windowswechat",
"expect" :
{
- "name" : "WeChat(Win) Desktop",
+ "name" : "WeChat",
"version" : "4.0.1301.400",
"major" : "4"
}
@@ -1475,7 +2346,8 @@
{
"name" : "GSA",
"version" : "30.1.161623614",
- "major" : "30"
+ "major" : "30",
+ "type" : "inapp"
}
},
{
@@ -1483,35 +2355,27 @@
"ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0",
"expect" :
{
- "name" : "MetaSr"
- }
- },
- {
- "desc" : "LieBao Browser",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.154 Safari/537.36 LBBROWSER",
- "expect" :
- {
- "name" : "LBBROWSER"
+ "name" : "Sogou Explorer",
+ "version" : "1.0",
+ "major" : "1"
}
},
{
- "desc" : "BaiDu Browser",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 BIDUBrowser/8.7 Safari/537.36",
+ "desc" : "Sogou Mobile Browser",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 SogouMSE,SogouMobileBrowser/3.7.4",
"expect" :
{
- "name" : "BIDUBrowser",
- "version" : "8.7",
- "major" : "8"
+ "name" : "Sogou Mobile",
+ "version" : "3.7.4",
+ "major" : "3"
}
},
{
- "desc" : "2345 Browser",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.90 Safari/537.36 2345Explorer/9.2.1.17116",
+ "desc" : "LieBao Browser",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.154 Safari/537.36 LBBROWSER",
"expect" :
{
- "name" : "2345Explorer",
- "version" : "9.2.1.17116",
- "major" : "9"
+ "name" : "LBBROWSER"
}
},
{
@@ -1569,7 +2433,48 @@
"ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [LinkedInApp]",
"expect" :
{
- "name" : "LinkedIn"
+ "name" : "LinkedIn",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Links in Linux",
+ "ua" : "Links (2.xpre7; Linux 2.4.18 i586; x)",
+ "expect" :
+ {
+ "name" : "Links",
+ "version" : "2.xpre7",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "Links in Mac",
+ "ua" : "Links (2.1pre33; Darwin 8.11.0 Power Macintosh; 169x55)",
+ "expect" :
+ {
+ "name" : "Links",
+ "version" : "2.1pre33",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "Links in NetBSD",
+ "ua" : "Links (2.29; NetBSD 10.0 i386; GNU C 10.5; x)",
+ "expect" :
+ {
+ "name" : "Links",
+ "version" : "2.29",
+ "major" : "2"
+ }
+ },
+ {
+ "desc" : "Links in FreeBSD",
+ "ua" : "Links (2.1pre15; FreeBSD 5.3-RELEASE i386; 196x84)",
+ "expect" :
+ {
+ "name" : "Links",
+ "version" : "2.1pre15",
+ "major" : "2"
}
},
{
@@ -1627,5 +2532,190 @@
"version": "9.0",
"major" : "9"
}
+ },
+ {
+ "desc" : "KakaoTalk App Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 12; SM-G988N Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.79 Mobile Safari/537.36;KAKAOTALK 2409760",
+ "expect" : {
+ "name" : "KAKAOTALK",
+ "version": "2409760",
+ "major" : "2409760",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "KakaoStory App Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 12; SM-G988N Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.79 Mobile Safari/537.36 KAKAOSTORY/6.8.3_21046",
+ "expect" : {
+ "name" : "KAKAOSTORY",
+ "version": "6.8.3_21046",
+ "major" : "6",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "KakaoTalk App iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU; iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 BizWebView KAKAOTALK 9.7.6",
+ "expect" : {
+ "name" : "KAKAOTALK",
+ "version": "9.7.6",
+ "major" : "9",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Naver App Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 12; SM-G988N Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/90.0.4430.232 Whale/1.0.0.0 Crosswalk/26.90.3.21 Mobile Safari/537.36 NAVER(inapp; search; 1010; 11.11.2)",
+ "expect" : {
+ "name" : "NAVER",
+ "version": "11.11.2",
+ "major" : "11",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Naver App iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Mobile/15E148 Safari/605.1 NAVER(inapp; search; 720; 10.25.0; 11PRO)",
+ "expect" : {
+ "name" : "NAVER",
+ "version": "10.25.0",
+ "major" : "10",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Daum App Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 11; SM-G970N Build/RP1A.200720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 DaumApps/7.5.0 DaumDevice/mobile",
+ "expect" : {
+ "name" : "Daum",
+ "version": "7.5.0",
+ "major" : "7",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Daum App iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Safari/605.1.15 Mobile/15E148 DaumApps/7.5.1 DaumDevice/mobile",
+ "expect" : {
+ "name" : "Daum",
+ "version": "7.5.1",
+ "major" : "7",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "TikTok",
+ "ua" : "Mozilla/5.0 (Linux; Android 11; 21061119AG Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.131 Mobile Safari/537.36 trill_2022109040 JsSdk/1.0 NetType/MOBILE Channel/googleplay AppName/musical_ly app_version/21.9.4 ByteLocale/ru-RU ByteFullLocale/ru-RU Region/KG BytedanceWebview/d8a21c6",
+ "expect" : {
+ "name" : "TikTok",
+ "version": "21.9.4",
+ "major" : "21",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "TikTok",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 musical_ly_21.1.0 JsSdk/2.0 NetType/4G Channel/App Store ByteLocale/ru Region/RU ByteFullLocale/ru-RU isDarkMode/1 WKWebView/1 BytedanceWebview/d8a21c6",
+ "expect" : {
+ "name" : "TikTok",
+ "version": "21.1.0",
+ "major" : "21",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "TikTok",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; STK-LX1 Build/HONORSTK-LX1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/110.0.5481.153 Mobile Safari/537.36 musical_ly_2022803040 JsSdk/1.0 NetType/WIFI Channel/huaweiadsglobal_int AppName/musical_ly app_version/28.3.4 ByteLocale/en ByteFullLocale/en Region/IQ Spark/1.2.7-alpha.8 AppVersion/28.3.4 PIA/1.5.11 BytedanceWebview/d8a21c6",
+ "expect" : {
+ "name" : "TikTok",
+ "version": "28.3.4",
+ "major" : "28",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Chrome Mobile",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.1.2; Nexus 5X Build/N2G47W) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Mobile Chrome",
+ "version" : "58.0.3029.83",
+ "major" : "58"
+ }
+ },
+ {
+ "desc" : "Firefox Mobile",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.1.2; Nexus 5X Build/N2G47W) AppleWebKit/537.36 (KHTML, like Gecko) FxiOS/7.5b3349 Mobile/14F89 Safari/603.2.4",
+ "expect" :
+ {
+ "name" : "Mobile Firefox",
+ "version" : "7.5b3349",
+ "major" : "7"
+ }
+ },
+ {
+ "desc" : "Firefox Mobile",
+ "ua" : "Mozilla/5.0 (Android 5.0; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0",
+ "expect" :
+ {
+ "name" : "Mobile Firefox",
+ "version" : "41.0",
+ "major" : "41"
+ }
+ },
+ {
+ "desc" : "Snapchat",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Snapchat/12.33.0.36 (like Safari/8614.1.25.0.31, panda)",
+ "expect" :
+ {
+ "name" : "Snapchat",
+ "version" : "12.33.0.36",
+ "major" : "12",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Twitter",
+ "ua" : "Mozilla/5.0 (Linux; Android 13; CPH2531 Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/123.0.6312.120 Mobile Safari/537.36 TwitterAndroid",
+ "expect" :
+ {
+ "name" : "Twitter",
+ "version" : "undefined",
+ "major" : "undefined",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Twitter",
+ "ua" : "Mozilla/5.0 (iPad; CPU OS 15_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/19H12 Twitter for iPhone/10.34",
+ "expect" :
+ {
+ "name" : "Twitter",
+ "version" : "10.34",
+ "major" : "10",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Zalo on iOS",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Zalo/20.05.01 Mobile/15E148",
+ "expect" :
+ {
+ "name" : "Zalo",
+ "version" : "20.05.01",
+ "major" : "20",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Zalo on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; Vsmart Live Build/QKQ1.190918.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36 Zalo/20.04.02.r1",
+ "expect" :
+ {
+ "name" : "Zalo",
+ "version" : "20.04.02.r1",
+ "major" : "20",
+ "type" : "inapp"
+ }
}
-]
+]
\ No newline at end of file
diff --git a/test/cpu-test.json b/test/data/ua/cpu/cpu-all.json
similarity index 66%
rename from test/cpu-test.json
rename to test/data/ua/cpu/cpu-all.json
index cf314f9c8..e9b749e7d 100644
--- a/test/cpu-test.json
+++ b/test/data/ua/cpu/cpu-all.json
@@ -7,6 +7,14 @@
"architecture" : "ia32"
}
},
+ {
+ "desc" : "i686",
+ "ua" : "Mozilla/5.0 (X11; U; CrOS i686 9.10.0; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.253.0 Safari/532.5",
+ "expect" :
+ {
+ "architecture" : "ia32"
+ }
+ },
{
"desc" : "i386",
"ua" : "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.7) Gecko/20040628 Epiphany/1.2.6",
@@ -23,6 +31,46 @@
"architecture" : "amd64"
}
},
+ {
+ "desc" : "Vivaldi on Windows",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Vivaldi/6.0.2979.18",
+ "expect" :
+ {
+ "architecture" : "amd64"
+ }
+ },
+ {
+ "desc" : "Vivaldi on Windows",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Vivaldi/6.0.2979.18",
+ "expect" :
+ {
+ "architecture" : "amd64"
+ }
+ },
+ {
+ "desc" : "Vivaldi on Linux",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Vivaldi/6.0.2979.18",
+ "expect" :
+ {
+ "architecture" : "amd64"
+ }
+ },
+ {
+ "desc" : "Vivaldi on Linux",
+ "ua" : "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Vivaldi/6.0.2979.18",
+ "expect" :
+ {
+ "architecture" : "ia32"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO M2 Pro",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; POCO M2 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 YaBrowser/22.11.7.42.00 SA/3 Mobile Safari/537.36",
+ "expect" :
+ {
+ "architecture" : "arm64"
+ }
+ },
{
"desc" : "win64",
"ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C)",
@@ -39,6 +87,22 @@
"architecture" : "amd64"
}
},
+ {
+ "desc" : "Alpha",
+ "ua" : "Mozilla/3.01 (WinNT; I) [AXP]",
+ "expect" :
+ {
+ "architecture" : "alpha"
+ }
+ },
+ {
+ "desc" : "Alpha",
+ "ua" : "Mozilla/5.0 (X11; OpenBSD alpha; rv:78.0) Gecko/20100101 Firefox/78.0",
+ "expect" :
+ {
+ "architecture" : "alpha"
+ }
+ },
{
"desc" : "ARM",
"ua" : "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537",
@@ -127,6 +191,22 @@
"architecture" : "arm64"
}
},
+ {
+ "desc" : "Google Search App",
+ "ua" : "Mozilla/5.0 (Linux; Android 9; JAT-LX1 Build/HONORJAT-LX1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.96 Mobile Safari/537.36 GoogleApp/11.11.10.21.arm",
+ "expect" :
+ {
+ "architecture" : "arm"
+ }
+ },
+ {
+ "desc" : "Google Search App",
+ "ua" : "Mozilla/5.0 (Linux; Android 6.0; M5s Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.147 Mobile Safari/537.36 GSA/12.40.17.23.arm64",
+ "expect" :
+ {
+ "architecture" : "arm64"
+ }
+ },
{
"desc" : "Pocket PC",
"ua" : "Opera/9.7 (Windows Mobile; PPC; Opera Mobi/35166; U; en) Presto/2.2.1",
@@ -206,5 +286,21 @@
{
"architecture" : "irix64"
}
+ },
+ {
+ "desc" : "68k",
+ "ua" : "'Mozilla/1.1 (Macintosh; U; 68K)'",
+ "expect" :
+ {
+ "architecture" : "68k"
+ }
+ },
+ {
+ "desc" : "x86",
+ "ua" : "Mozilla/5.0 (Photon; U; QNX x86pc; en-US; rv:1.8.1.20) Gecko/20090127 BonEcho/2.0.0.20",
+ "expect" :
+ {
+ "architecture" : "ia32"
+ }
}
]
diff --git a/test/data/ua/device/_others.json b/test/data/ua/device/_others.json
new file mode 100644
index 000000000..06a70fef7
--- /dev/null
+++ b/test/data/ua/device/_others.json
@@ -0,0 +1,320 @@
+[
+ {
+ "desc": "Generic Android Device",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 9; X96mini_RP Build/X96mini_RP)",
+ "expect": {
+ "vendor": "Generic",
+ "model": "X96mini_RP"
+ }
+ },
+ {
+ "desc": "Generic Android Device",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; i980 Build/MRA58K)",
+ "expect": {
+ "vendor": "Generic",
+ "model": "i980"
+ }
+ },
+ {
+ "desc": "K",
+ "ua": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "K",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Unknown VR Device",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.2; Unknown Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile VR Safari/537.36",
+ "expect": {
+ "model": "Unknown",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Desktop (IE11 with Tablet string)",
+ "ua": "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; Tablet PC 2.0; GWX:MANAGED; rv:11.0) like Gecko",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "undefined"
+ }
+ },
+ {
+ "desc": "Mobile (DuckDuckGo mobile browser)",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.131 Mobile DuckDuckGo/5 Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ChangHong Android TV",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.1.1; zh-cn; ChangHong Android TV Build/LMY49J) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.8 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "ChangHong",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "MStar Android TV",
+ "ua": "Mozilla/5.0 (Linux; Android 4.3.1; MStar Android TV Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.95 Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "MStar",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "ONIDA Android TV",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; ONIDA Android TV Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2542.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "ONIDA",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "JVC LT-43V55LFA Smart TV",
+ "ua": "Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 OPR/40.0.2207.0 OMI/4.9.0.237.DOM3-OPT.245 Model/Vestel-MB211 VSTVB MB200 HbbTV/1.2.1 (; JVC; MB211; 3.19.4.2; _TV_NT72563_2017 SmartTvA/3.0.0",
+ "expect": {
+ "vendor": "JVC",
+ "model": "MB211",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "JVC LT-43V65LUA Smart TV",
+ "ua": "Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 OPR/40.0.2207.0 OMI/4.9.0.237.DOM3-OPT.245 Model/Vestel-MB130 VSTVB MB100 HbbTV/1.2.1 (; JVC; MB130; 5.7.20.0; _TV_G10_2017;) SmartTvA/3.0.0",
+ "expect": {
+ "vendor": "JVC",
+ "model": "MB130",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Loewe Smart TV",
+ "ua": "Mozilla/5.0 (Linux; U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 OPR/46.0.2207.0 LOEWE-SL410/5.2.0.0 HbbTV/1.4.1 (; LOEWE; SL410; LOH/5.2.0.0;;) FVC/3.0 (LOEWE; SL410;) CE-HTML/1.0 Config (L:deu,CC:DEU) NETRANGEMMH",
+ "expect": {
+ "vendor": "LOEWE",
+ "model": "SL410",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Issue #747",
+ "ua": "python-requests/2.25.1",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "undefined"
+ }
+ },
+ {
+ "desc": "Issue #454",
+ "ua": "Mosamzilla/5.0 (Windows; U; Win98; en-US; rv:1.7.5) Gecko/20050603 Netscape/8.0.2",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "undefined"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV",
+ "ua": "Mozilla/5.0 (SMART-TV; X11; Linux armv7l) AppleWebkit/537.42 (KHTML, like Gecko) Safari/537.42",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Android Phone Unidentified Vendor (docomo F-04K)",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; F-04K Build/V15R060P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36",
+ "expect": {
+ "model": "F-04K",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Android Tablet Unidentified Vendor (docomo F-02K)",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; F-02K Build/V44R059G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Safari/537.36",
+ "expect": {
+ "model": "F-02K",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Android Tablet Unidentified Vendor (docomo d-02K)",
+ "ua": "Mozilla/5.0 (Linux; Android 9; d-02K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Safari/537.36",
+ "expect": {
+ "model": "d-02K",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Android TV",
+ "ua": "Mozilla/5.0 (Linux; Android 10; 2020/2021 UHD Android TV Build/QTG3.201102.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Unknown Mobile using Firefox",
+ "ua": "Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Unknown Tablet using Firefox",
+ "ua": "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Unknown Mobile using Focus for Android",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Unknown Tablet using Focus for Android",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0 Chrome/59.0.3029.83 Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Unknown Device using Focus for Android with GeckoView",
+ "ua": "Mozilla/5.0 (Android 7.0; Mobile; rv:62.0) Gecko/62.0 Firefox/62.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Unknown Mobile using Firefox OS",
+ "ua": "Mozilla/5.0 (Mobile; rv:26.0) Gecko/26.0 Firefox/26.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Unknown Tablet using Firefox OS",
+ "ua": "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Unknown TV using Firefox OS",
+ "ua": "Mozilla/5.0 (TV; rv:44.0) Gecko/44.0 Firefox/44.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Unknown TV",
+ "ua": "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.127 Large Screen Safari/533.4 GoogleTV/ 162671",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Unknown TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen WebAppManager Safari/537.41",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Unknown VR Device using Firefox Reality",
+ "ua": "Mozilla/5.0 (Android 10; Mobile VR; rv:123.0) Gecko/123.0 Firefox/123.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Unknown VR Device using Wolvic",
+ "ua": "Mozilla/5.0 (Android 14; Mobile VR; rv:128.0) Gecko/128.0 Firefox/128.0 Wolvic/1.8",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Smart TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 HbbTV/1.6.1 ( DRM; LGE/ATMACA/GRAETZ; GR32S1470; WEBOS22 04.41.53; W22_K8AP; DTV_C22L;) LaTivu_1.0.1_2022",
+ "expect": {
+ "vendor": "GRAETZ",
+ "model": "GR32S1470",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Smart TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 HbbTV/1.5.1 (+DRM; LGE/DUALSHINE/SKYTECH; ST-5090; WEBOS5.0 04.50.63; W50_K6LP; DTV_C20P;)",
+ "expect": {
+ "vendor": "SKYTECH",
+ "model": "ST-5090",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Smart TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 HbbTV/1.6.1 (+DRM; LGE/SILICONPLAYER/Hyundai; 50HYN3205; WEBOS22 04.42.26; W22_K8LP; DTV_C22P;)",
+ "expect": {
+ "vendor": "Hyundai",
+ "model": "50HYN3205",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "PDA with Windows CE",
+ "ua": "Mozilla/4.0 (PDA; Windows CE/1.0.1) NetFront/3.0",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc" : "Windows IoT",
+ "ua" : "Mozilla/5.0 (Windows IoT 10.0; Android 6.0.1; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Mobile Safari/537.36 Edge/18.17763",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "embedded"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/acer.json b/test/data/ua/device/acer.json
new file mode 100644
index 000000000..7eff9d80e
--- /dev/null
+++ b/test/data/ua/device/acer.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc": "Acer Iconia A1-810",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; A1-810 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Safari/537.36",
+ "expect": {
+ "vendor": "Acer",
+ "model": "A1-810",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/advan.json b/test/data/ua/device/advan.json
new file mode 100644
index 000000000..16a233106
--- /dev/null
+++ b/test/data/ua/device/advan.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "Advan M4",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0; ADVAN M4 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36 OPR/28.0.2254.119214",
+ "expect": {
+ "vendor": "ADVAN",
+ "model": "M4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Advan S40",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; ADVAN S40 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Mobile Safari/537.36 EdgA/79.0.309.58",
+ "expect": {
+ "vendor": "ADVAN",
+ "model": "S40",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Advan Sketsa 2",
+ "ua": "Mozilla/5.0 (Linux; Android 11; ADVAN 1011) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Safari/537.36",
+ "expect": {
+ "vendor": "ADVAN",
+ "model": "1011",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/alcatel.json b/test/data/ua/device/alcatel.json
new file mode 100644
index 000000000..3cd613ee9
--- /dev/null
+++ b/test/data/ua/device/alcatel.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "Alcatel 4056W",
+ "ua": "Mozilla/5.0 (Mobile; ALCATEL 4056W; rv:84.0) Gecko/84.0 Firefox/84.0 KAIOS/3.0",
+ "expect": {
+ "vendor": "ALCATEL",
+ "model": "4056W",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Alcatel A564C",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; ALCATEL A564C Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ALCATEL",
+ "model": "A564C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Alcatel Go Flip",
+ "ua": "Mozilla/5.0 (Mobile; ALCATEL4044T; rv:37.0) Gecko/37.0 Firefox/37.0 KaiOS/1.0",
+ "expect": {
+ "vendor": "ALCATEL",
+ "model": "4044T",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/amazon.json b/test/data/ua/device/amazon.json
new file mode 100644
index 000000000..6e60318e8
--- /dev/null
+++ b/test/data/ua/device/amazon.json
@@ -0,0 +1,137 @@
+[
+ {
+ "desc": "Kindle Fire HD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT Build/IML74K) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.4 Mobile Safari/535.19 Silk-Accelerated=true",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KFTT",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Kindle Fire HD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.4 Mobile Safari/535.19 Silk-Accelerated=true",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KFTT",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Echo Show 5",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; AEORK Build/LVY48F; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "AEORK",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Echo Show 8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1; AEOCH) AppleWebKit/537.36 (KHTML, like Gecko) Silk/77.2.21 like Chrome/77.0.3865.92 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "AEOCH",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Echo Show 8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.2; AEOCW) AppleWebKit/537.36 (KHTML, like Gecko) Silk/106.3.3 like Chrome/106.0.5249.170 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "AEOCW",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Echo Show 15",
+ "ua": "Mozilla/5.0 (Linux; Android 9; AEOHY) AppleWebKit/537.36 (KHTML, like Gecko) Silk/112.6.3 like Chrome/112.0.5615.213 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "AEOHY",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Echo Dot",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 5.1.1; AEOBC Build/LVY48F)",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "AEOBC",
+ "type": "embedded"
+ }
+ },
+ {
+ "desc": "Amazon Kindle Fire Tablet",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4.3; en-us; KFSAWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.66 like Chrome/39.0.2171.93 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KFSAWI",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Amazon Kindle Fire Tablet",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4.3; en-us; KFSAWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.66 like Chrome/39.0.2171.93 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KFSAWI",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Amazon Kindle Fire Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 9; KFMAWI Build/PS7312; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KFMAWI",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Amazon Fire TV",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; AFTB Build/JDQ39) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.173 Mobile Safari/537.22",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "B",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Amazon Fire TV",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; AFTT) AppleWebKit/537.36 (KHTML, like Gecko) Silk/86.3.20 like Chrome/86.0.4240.198 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "T",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Amazon Fire TV",
+ "ua": "Mozilla/5.0 (Linux; Android 9; AFTKA Build/PS7633.3445N; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.160 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KA",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Amazon Fire 7",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; KFAUWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/80.5.3 like Chrome/80.0.3987.162 Safari/537.36",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "KFAUWI",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Amazon Alexa Echo Show",
+ "ua": "AlexaWebMediaPlayer/1.0.200641.0 (Linux;Android 5.1.1)",
+ "expect": {
+ "vendor": "Amazon",
+ "model": "Alexa",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/apple.json b/test/data/ua/device/apple.json
new file mode 100644
index 000000000..ea68e8acc
--- /dev/null
+++ b/test/data/ua/device/apple.json
@@ -0,0 +1,137 @@
+[
+ {
+ "desc": "Apple Desktop",
+ "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15",
+ "expect": {
+ "vendor": "Apple",
+ "model": "Macintosh",
+ "type": "undefined"
+ }
+ },
+ {
+ "desc": "Apple Watch",
+ "ua": "atc/1.0 watchOS/7.3.3 model/Watch4,2 hwp/t8006 build/18S830 (6; dt:191)",
+ "expect": {
+ "vendor": "Apple",
+ "model": "watch",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "iPad using UCBrowser",
+ "ua": "Mozilla/5.0 (iPad; U; CPU OS 11_2 like Mac OS X; zh-CN; iPad5,3) AppleWebKit/534.46 (KHTML, like Gecko) UCBrowser/3.0.1.776 U3/ Mobile/10A403 Safari/7543.48.3",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPad",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "iPad Air",
+ "ua": "Mozilla/5.0 (iPad; CPU OS 12_4_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 [FBAN/FBIOS;FBDV/iPad4,1;FBMD/iPad;FBSN/iOS;FBSV/12.4.5;FBSS/2;FBID/tablet;FBLC/en_US;FBOP/5;FBCR/]",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPad",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "iPad using Facebook Browser",
+ "ua": "Mozilla/5.0 (iPad; CPU OS 14_4_2 like Mac OS X) WebKit/8610 (KHTML, like Gecko) Mobile/18D70 [FBAN/FBIOS;FBDV/iPad7,11;FBMD/iPad;FBSN/iOS;FBSV/14.4.2;FBSS/2;FBID/tablet;FBLC/en_US;FBOP/5]",
+ "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",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPod touch",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "iPhone",
+ "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone",
+ "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/]",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "iPhone SE using Facebook App",
+ "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/]",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "iPhone 11 Pro Max",
+ "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/iPhone12,5;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "iPhone XS",
+ "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/iPhone11,2;FBMD/iPhone;FBSN/iOS;FBSV/13.3.1;FBSS/3;FBID/phone;FBLC/en_US;FBOP/5;FBCR/]",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "iPod touch",
+ "ua": "Mozilla/5.0 (iPod touch; CPU iPhone OS 7_0_2 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A501 Safari/9537.53",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPod touch",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "FaceBook Mobile App",
+ "ua": "[FBAN/FBIOS;FBAV/283.0.0.44.117;FBBV/238386386;FBDV/iPhone12,1;FBMD/iPhone;FBSN/iOS;FBSV/13.6.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBRV/240127608]",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone12,1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Apple HomePod",
+ "ua": "AppleCoreMedia/1.0.0.15D61 (HomePod; U; CPU OS 11_2_5 like Mac OS X; en_us)",
+ "expect": {
+ "vendor": "Apple",
+ "model": "HomePod",
+ "type": "embedded"
+ }
+ },
+ {
+ "desc": "Issue #519",
+ "ua": "ios/iPhone/14.2/SOME_CUSTOM_APP_VERSION",
+ "expect": {
+ "vendor": "Apple",
+ "model": "iPhone",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/archos.json b/test/data/ua/device/archos.json
new file mode 100644
index 000000000..53628c535
--- /dev/null
+++ b/test/data/ua/device/archos.json
@@ -0,0 +1,227 @@
+[
+ {
+ "desc": "Archos 5",
+ "ua": "Mozilla/5.0 (Linux; U; Android 1.6; fr-fr; Archos5 Build/Donut) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1",
+ "expect": {
+ "vendor": "Archos",
+ "model": "5",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 40b Titanium Surround",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Archos 40b Titanium Surround Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "40b Titanium Surround",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 40c Titanium v2",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; ARCHOS 40C TIv2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "40C TIv2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 45 Neon",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Archos 45 Neon Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "45 Neon",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 45 Neon",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; AC45NE Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 YaBrowser/15.6.2311.6088.00 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "AC45NE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 45B Helium",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; AC45BHE Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.109 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "AC45BHE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 45B Titanium",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Archos 45B Titanium) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "45B Titanium",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 50 Cesium",
+ "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; ARCHOS; AC50CE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586",
+ "expect": {
+ "vendor": "Archos",
+ "model": "AC50CE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 50B Helium 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.4; AC50BHE Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "AC50BHE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 55 diamond Selfie",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Archos 55 diamond Selfie Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.91 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "55 diamond Selfie",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Archos 80 G9",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; zh-tw; ARCHOS 80G9 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
+ "expect": {
+ "vendor": "Archos",
+ "model": "80G9",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 80 Xenon",
+ "ua": "Mozilla/5.0 (Linux; Android 4.1.2; Archos 80 Xenon Build/JZO54K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Safari/537.36 OPR/50.6.2426.201126",
+ "expect": {
+ "vendor": "Archos",
+ "model": "80 Xenon",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 97c Platinum",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Archos 97c Platinum Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "97c Platinum",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 101 Access 3G V2",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; Archos Access 101 3G V2 Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.130 Safari/537.36[FBAN/EMA;FBLC/pt_PT;FBAV/360.0.0.7.53;]",
+ "expect": {
+ "vendor": "Archos",
+ "model": "Access 101 3G V2",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 101 Oxygen 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Archos 101 Oxygen 4G Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Safari/537.36 [FB_IAB/FB4A;FBAV/374.0.0.20.109;]",
+ "expect": {
+ "vendor": "Archos",
+ "model": "101 Oxygen 4G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 101 Platinum 3G V2",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; AC101PL3GV2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "AC101PL3GV2",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 101B Helium 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; AC101BHE Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "AC101BHE",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos 101s Oxygen Ardoiz",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Archos Oxygen 101S ARDOIZ Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.102 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "Oxygen 101S ARDOIZ",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos GAMEPAD2",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; ARCHOS GAMEPAD2 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.94 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "GAMEPAD2",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos Hello 7",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Archos Hello 7 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.53 Safari/537.36 GoogleApp/13.24.9.26.arm64",
+ "expect": {
+ "vendor": "Archos",
+ "model": "Hello 7",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos Sense 101 X",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 7.0; Archos Sense 101 X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 YaBrowser/20.2.0.215.01 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "Sense 101 X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos T101 FHD WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 13; ARCHOS T101 FHD WiFi Build/T00624; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/124.0.6367.159 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "T101 FHD WiFi",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos Tikeasy 10d",
+ "ua": "Mozilla/5.0 (Linux; Android 13; Tikeasy 10d Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.260 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "Tikeasy 10d",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos T96 WIFI",
+ "ua": "Mozilla/5.0 (Linux; Android 11; ARCHOS T96 WIFI_EEA Build/RP1A.201005.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/102.0.5005.78 Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "T96 WIFI_EEA",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Archos X67 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; X67 5G Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.58 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Archos",
+ "model": "X67 5G",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/asus.json b/test/data/ua/device/asus.json
new file mode 100644
index 000000000..e5fee47bd
--- /dev/null
+++ b/test/data/ua/device/asus.json
@@ -0,0 +1,164 @@
+[
+ {
+ "desc": "ASUS Nexus 7",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 7 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.99 Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "Nexus 7",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "ASUS Padfone",
+ "ua": "Mozilla/5.0 (Linux; Android 4.1.1; PadFone 2 Build/JRO03L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "PadFone",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "ASUS ZenPad 10",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; P00C Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "P00C",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "ASUS ZenPad Z8s",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; ASUS_P00J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Safari/537.36\n",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "P00J",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "ASUS ROG",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; ZS600KL Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "ZS600KL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS ROG II",
+ "ua": "Mozilla/5.0 (Linux; Android 9; ASUS_I001DA Build/PKQ1.190414.001; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "I001DA",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone 2",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0; ASUS ZenFone 2 Build/LRX22C) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "ZenFone 2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone 3 Deluxe",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; ASUS_Z016D Build/MXB48T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "Z016D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone 5",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0; ZE620KL Build/OPR1.170623.032) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "ZE620KL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone 7",
+ "ua": "Mozilla/5.0 (Linux; Android 10; ASUS_I002D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.81 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "I002D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone 7 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; ZS671KS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "ZS671KS",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone Max Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; ZB602KL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "ZB602KL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone Max Pro (M1)",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; ASUS_X00TD Build/OPM1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "X00TD",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone Max M2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; ASUS_X01AD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "X01AD",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone Max Pro M2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; ASUS_X01BDA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "X01BDA",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone Go",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; ASUS_X009DA Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "X009DA",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS Zenfone 2 Laser",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; ASUS_Z00ED) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "Z00ED",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "ASUS ZenWatch",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.1; ASUS ZenWatch Build/LWX48S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/19.77.34.5 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ASUS",
+ "model": "ZenWatch",
+ "type": "wearable"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/blackberry.json b/test/data/ua/device/blackberry.json
new file mode 100644
index 000000000..f809d5541
--- /dev/null
+++ b/test/data/ua/device/blackberry.json
@@ -0,0 +1,47 @@
+[
+ {
+ "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",
+ "expect": {
+ "vendor": "BlackBerry",
+ "model": "STV100-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BlackBerry Keyone",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; BBB100-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.111 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BlackBerry",
+ "model": "BBB100-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BlackBerry Key2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; BBF100-1 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BlackBerry",
+ "model": "BBF100-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "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",
+ "expect": {
+ "vendor": "BlackBerry",
+ "model": "BBE100-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Blackview 4900Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 12; BV4900Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "BV4900Pro",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/blu.json b/test/data/ua/device/blu.json
new file mode 100644
index 000000000..969d08682
--- /dev/null
+++ b/test/data/ua/device/blu.json
@@ -0,0 +1,101 @@
+[
+ {
+ "desc": "BLU Grand X LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; BLU Grand X LTE Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BLU",
+ "model": "Grand X LTE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU Neo Energy Mini",
+ "ua": "Mozilla/5.0 (Linux; Android 10; BLU_NEO_ENERGY_MINI Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.54 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BLU",
+ "model": "NEO_ENERGY_MINI",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU NEO X PLUS",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; BLU NEO X PLUS Build/N090U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BLU",
+ "model": "NEO X PLUS",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU STUDIO X MINI",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; BLU STUDIO X MINI Build/S0150UU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BLU",
+ "model": "STUDIO X MINI",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU Tank Mega",
+ "ua": "Mozilla/5.0 (Mobile; BLU_TankMega_3G; rv:48.0; CAEN) Gecko/48.0 Firefox/48.0 KAIOS/2.5.1.1",
+ "expect": {
+ "vendor": "BLU",
+ "model": "TankMega_3G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU TOUCHBOOK G7",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0; BLU TOUCHBOOK G7 Build/LRX21M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.107 Safari/537.36 OPR/29.0.1809.91837",
+ "expect": {
+ "vendor": "BLU",
+ "model": "TOUCHBOOK G7",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU Vivo 5 Mini",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; Vivo 5 Mini Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "BLU",
+ "model": "Vivo 5 Mini",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU VIVO AIR LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.2; BLU VIVO AIR LTE Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.107 Mobile Safari/537.36 OPR/29.0.1809.91837",
+ "expect": {
+ "vendor": "BLU",
+ "model": "VIVO AIR LTE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU VIVO GO",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9; VIVO GO Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/72.0.3626.121 Mobile Safari/537.36 OPR/50.0.2254.149182",
+ "expect": {
+ "vendor": "BLU",
+ "model": "VIVO GO",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU Vivo One Plus 2019",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Vivo One Plus 2019 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/277.0.0.41.126;]",
+ "expect": {
+ "vendor": "BLU",
+ "model": "Vivo One Plus 2019",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "BLU VIVO SELFIE",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.0; es-LA; BLU VIVO SELFIE Build/LRX21M) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/11.3.5.972 U3/0.8.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "BLU",
+ "model": "VIVO SELFIE",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/cat.json b/test/data/ua/device/cat.json
new file mode 100644
index 000000000..7c5710d30
--- /dev/null
+++ b/test/data/ua/device/cat.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc": "Cat B15Q",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; B15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Cat",
+ "model": "B15Q",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Cat B35",
+ "ua": "Mozilla/5.0 (Mobile; CAT B35; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5.1",
+ "expect": {
+ "vendor": "Cat",
+ "model": "B35",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Cat S22 Flip",
+ "ua": "Mozilla/5.0 (Linux; Android 11; S22 FLIP Build/RKQ1.210416.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.165 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Cat",
+ "model": "S22 FLIP",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Cat S62 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; S62 Pro Build/RKQ1.210406.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/94.0.4606.85 Mobile Safari/537.36 GSA/12.34.17.23.arm64",
+ "expect": {
+ "vendor": "Cat",
+ "model": "S62 Pro",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/energizer.json b/test/data/ua/device/energizer.json
new file mode 100644
index 000000000..ec1ec4762
--- /dev/null
+++ b/test/data/ua/device/energizer.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "Energizer Energy 400",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Energy400 Build/MRA58K test-keys; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.158 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/172.0.0.66.93;]",
+ "expect": {
+ "vendor": "Energizer",
+ "model": "Energy400",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Energizer Energy 400S",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Energy 400S Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.85 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Energizer",
+ "model": "Energy 400S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Energizer Ultimate 65G",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Energizer Ultimate 65G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Energizer",
+ "model": "Ultimate 65G",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/facebook.json b/test/data/ua/device/facebook.json
new file mode 100644
index 000000000..f08f81f31
--- /dev/null
+++ b/test/data/ua/device/facebook.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Oculus Quest",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Quest) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/15.0.0.0.22.280317669 SamsungBrowser/4.0 Chrome/89.0.4389.90 VR Safari/537.36",
+ "expect": {
+ "vendor": "Facebook",
+ "model": "Quest",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Oculus Quest 2",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Quest 2) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/15.0.0.0.22.280317669 SamsungBrowser/4.0 Chrome/89.0.4389.90 VR Safari/537.36",
+ "expect": {
+ "vendor": "Facebook",
+ "model": "Quest 2",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Oculus Quest 3",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; Quest 3) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/31.4.0.6.51.566757996 Chrome/120.0.6099.283 VR Safari/537.36",
+ "expect": {
+ "vendor": "Facebook",
+ "model": "Quest 3",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Oculus Quest Pro",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; Quest Pro) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/24.4.0.22.60.426469926 SamsungBrowser/4.0 Chrome/106.0.5249.181 VR Safari/537.36",
+ "expect": {
+ "vendor": "Facebook",
+ "model": "Quest Pro",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Portal TV",
+ "ua": "Mozilla/5.0 (Linux; Android 9; PortalTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.120 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Facebook",
+ "model": "PortalTV",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/fairphone.json b/test/data/ua/device/fairphone.json
new file mode 100644
index 000000000..e341cf7b1
--- /dev/null
+++ b/test/data/ua/device/fairphone.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "Fairphone 1U",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; FP1U Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Fairphone",
+ "model": "FP1U",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Fairphone 2",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.2; FP2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Fairphone",
+ "model": "FP2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Fairphone 3",
+ "ua": "Mozilla/5.0 (Linux; Android 9; FP3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Fairphone",
+ "model": "FP3",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/google.json b/test/data/ua/device/google.json
new file mode 100644
index 000000000..0897534e7
--- /dev/null
+++ b/test/data/ua/device/google.json
@@ -0,0 +1,614 @@
+[
+ {
+ "desc": "Google Chromecast with Google TV",
+ "ua": "Mozilla/5.0 (Linux; Android 12.0; Build/STTL.240206.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.0 Safari/537.36 CrKey/1.56.500000 DeviceType/AndroidTV",
+ "expect": {
+ "vendor": "Google",
+ "model": "Chromecast AndroidTV",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Google Chromecast Mini Smart Speaker",
+ "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.225 Safari/537.36 CrKey/1.56.500000 DeviceType/SmartSpeaker",
+ "expect": {
+ "vendor": "Google",
+ "model": "Chromecast SmartSpeaker",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Google Chromecast Third Generation",
+ "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.225 Safari/537.36 CrKey/1.56.500000 DeviceType/Chromecast",
+ "expect": {
+ "vendor": "Google",
+ "model": "Chromecast Third Generation",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Google Chromecast Nest Hub",
+ "ua": "Mozilla/5.0 (Fuchsia) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 CrKey/1.56.500000",
+ "expect": {
+ "vendor": "Google",
+ "model": "Chromecast Nest Hub",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Google Chromecast",
+ "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.84 Safari/537.36 CrKey/1.22.79313",
+ "expect": {
+ "vendor": "Google",
+ "model": "Chromecast",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Google Pixel C",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel C",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Google Pixel C",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.64 Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel C",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Google Pixel",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1; Pixel Build/NDE63V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.85 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 Build/OPM1.171019.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 2",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 2) AppleWebKit/537.36 (KHTML, like Gecko) Edg/57.0.986.6",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 2",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.2; G011A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Safari/537.36 OPR/68.3.3557.64528",
+ "expect": {
+ "vendor": "Google",
+ "model": "G011A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 2 XL",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Pixel 2 XL Build/OPM1.171019.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 2 XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 2 XL",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 2 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 2 XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 3",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3",
+ "ua": "Mozilla/5.0 (Linux; Android 11; G013A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G013A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3a",
+ "ua": "Mozilla/5.0 (Linux; Android 14; G020E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G020E",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3 XL",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 3 XL Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 3 XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3 XL",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 3 XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3a",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 3a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 3a",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 3a XL",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 3a XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 3a XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4",
+ "ua": "Mozilla/5.0 (Linux; Android 12; G020M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G020M",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4a",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 4a",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4a",
+ "ua": "Mozilla/5.0 (Linux; Android 13; G025J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G025J",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4a",
+ "ua": "Mozilla/5.0 (Linux; Android 12; GA02099) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GA02099",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4a",
+ "ua": "Mozilla/5.0 (Linux; Android 13; G025N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G025N",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4a (5G)",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel 4a (5G) Build/UP1A.231105.001.B2; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 4a (5G)",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 4 XL",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Pixel 4 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 4 XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 5",
+ "ua": "Mozilla/5.0 (Linux; Android 13; G5NZ6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G5NZ6",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 5",
+ "ua": "Mozilla/5.0 (Linux; Android 12; GD1YQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GD1YQ",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 5",
+ "ua": "Mozilla/5.0 (Linux; Android 12; GTT9Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GTT9Q",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 5",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.120 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 5",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 5a 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 14; G4S1M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G4S1M",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 5a 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; G1F8F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G1F8F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 6",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Pixel 6 Build/QP1A.190711.020) AppleWebKit/545.31 (KHTML, like Gecko) Firefox/109.0.2318.118 Mobile Safari/545.22",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 6",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 6 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 15; Pixel 6 Pro Build/AP4A.241205.013; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36 Line/15.0.0/IAB",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 6 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 6 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; G8VOU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G8VOU",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 7",
+ "ua": "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 7",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 7 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 15; Pixel 7 Pro Build/AP4A.250205.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.121 Mobile Safari/537.36 musical_ly_2023808030 BytedanceWebview/d8a21c6",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 7 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 7 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 14; GP4BC) AppleWebKit/537.46 (KHTML, like Gecko) Chrome/118.0.5993.80 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GP4BC",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 7 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 14; GE2AE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5993.48 Mobile Safari/537.46",
+ "expect": {
+ "vendor": "Google",
+ "model": "GE2AE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 7a",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel 7a Build/AP2A.240905.003; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 7a",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 8",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Build/UPB2.230407.014) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.2.7822.95 Mobile Safari/537.36 Vivaldi/5.2.9076.131",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 8",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 8",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro Build/UPB1.230309.017; Rooted) AppleWebKit/537.36 (KHTML, like Gecko) Puffin/10.9.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 8 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 8a",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Google Pixel 8a) Chrome/121.0.6167 Mobile",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 8a",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 8a",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GKV4X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GKV4X",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 8a",
+ "ua": "Mozilla/5.0 (Linux; Android 15; G8HHN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G8HHN",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 8a",
+ "ua": "Mozilla/5.0 (Linux; Android 15; G576D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G576D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.119 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 9",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9",
+ "ua": "Mozilla/5.0 (Linux; Android 15; G2YBB) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G2YBB",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GUR25) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GUR25",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GWVK6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GWVK6",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9",
+ "ua": "Mozilla/5.0 (Linux; Android 15; G1B60) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "G1B60",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro Build/AD1A.240530.047; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.170 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/506.1.0.74.27;IABMV/1;]",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 9 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GR83Y) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GR83Y",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GEC77) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GEC77",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro Fold",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel 9 Pro Fold) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 9 Pro Fold",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro Fold",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GC15S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GC15S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro Fold",
+ "ua": "Mozilla/5.0 (Linux; Android 15; GGH2X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "GGH2X",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel 9 Pro XL",
+ "ua": "Mozilla/5.0 (Linux; U; Android 15; Pixel 9 Pro XL Build/AP4A.250105.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.89 Mobile Safari/537.36 OPR/88.0.2254.75874",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel 9 Pro XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel Fold",
+ "ua": "Mozilla/5.0 (Linux; Android 15; Pixel Fold Build/AP4A.250105.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.97 Safari/537.36 [FB_IAB/FB4A;FBAV/497.0.0.40.36;IABMV/1;]",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel Fold",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Pixel Tablet Build/AP2A.240905.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel Tablet",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Google Pixel Watch",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 13; Google Pixel Watch Build/TWD4.231005.002)",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel Watch",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Google Pixel Watch 2",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 13; Google Pixel Watch 2 Build/TWD9.240605.001.A1)",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel Watch 2",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Google Pixel XL",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1; Pixel XL Build/NDE63X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.85 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixel XL",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Pixel XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixel XL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Google Pixelbook",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Google Pixelbook) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixelbook",
+ "type": "undefined"
+ }
+ },
+ {
+ "desc": "Google Pixelbook Go",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Google Pixelbook Go) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 OPR/62.3.3146.57763",
+ "expect": {
+ "vendor": "Google",
+ "model": "Pixelbook Go",
+ "type": "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/hmd.json b/test/data/ua/device/hmd.json
new file mode 100644
index 000000000..037148c41
--- /dev/null
+++ b/test/data/ua/device/hmd.json
@@ -0,0 +1,65 @@
+[
+ {
+ "desc": "HMD Barbie Phone",
+ "ua": "Mozilla/5.0 (Mobile; HMD Barbie Phone; rv:84.0) Gecko/84.0 Firefox/84.0 KAIOS/3.1",
+ "expect": {
+ "vendor": "HMD",
+ "model": "Barbie Phone",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HMD Fusion",
+ "ua": "Mozilla/5.0 (Linux; Android 14; HMD Fusion) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "HMD",
+ "model": "Fusion",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HMD Pulse",
+ "ua": "Mozilla/5.0 (Linux; Android 14; HMD Pulse) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "HMD",
+ "model": "Pulse",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HMD Pulse Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 14; HMD Pulse Plus) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "HMD",
+ "model": "Pulse Plus",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HMD Pulse Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 14; HMD Pulse Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "HMD",
+ "model": "Pulse Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HMD Skyline",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 14; HMD Skyline) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.1804 YaApp_Android/24.120.1 YaSearchBrowser/24.120.1 BroPP/1.0 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "HMD",
+ "model": "Skyline",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HMD Vibe",
+ "ua": "Mozilla/5.0 (Linux; Android 14; N159V Build/UKQ1.231025.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.111 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/497.0.0.47.36;]",
+ "expect": {
+ "vendor": "HMD",
+ "model": "N159V",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/honor.json b/test/data/ua/device/honor.json
new file mode 100644
index 000000000..321e51b1f
--- /dev/null
+++ b/test/data/ua/device/honor.json
@@ -0,0 +1,164 @@
+[
+ {
+ "desc": "Honor MagicPad 13 WiFi",
+ "ua": "Mozilla/5.0 (Linux; U; Android 13; zh-CN; GDI-W09 Build/HONORGDI-W09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/16.3.9.1290 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "GDI-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad 2",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; en-nz; JDN-W09 Build/HuaweiMediaPad) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 Chrome/37.0.0.0 MQQBrowser/6.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "JDN-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad 2",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9; zh-Hans-CN; JDN2-W09HN Build/HUAWEIJDN2-W09HN) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Quark/4.6.6.164 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "JDN2-W09HN",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad 7 10.1",
+ "ua": "Mozilla/5.0 (Linux; Android 12; AGM3-AL09HN Build/HONORAGM3-AL09HN; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.46 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/490.0.0.63.82;IABMV/1;]",
+ "expect": {
+ "vendor": "Honor",
+ "model": "AGM3-AL09HN",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad 8 12.0",
+ "ua": "Mozilla/5.0 (Linux; Android 12; HEY-W09 Build/HONORHEY-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "HEY-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad 9 12.1",
+ "ua": "Mozilla/5.0 (Linux; Android 13; HEY2-N09 Build/HONORHEY2-N09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36 [FB_IAB/FB4A;FBAV/465.0.0.63.83;]",
+ "expect": {
+ "vendor": "Honor",
+ "model": "HEY2-N09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad 9 12.1 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 14; HEY2-W09 Build/HONORHEY2-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.102 Safari/537.36 [FB_IAB/FB4A;FBAV/489.0.0.66.81;IABMV/1;]",
+ "expect": {
+ "vendor": "Honor",
+ "model": "HEY2-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad V7 Pro 11",
+ "ua": "Mozilla/5.0 (Linux; Android 12; BRT-AN09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 EdgA/109.0.1518.53",
+ "expect": {
+ "vendor": "Honor",
+ "model": "BRT-AN09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad V7 Pro 11 WiFi",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; zh-Hans-CN; BRT-W09 Build/HONORBRT-W09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 Quark/6.5.0.336 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "BRT-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad X6",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGR-W09HN Build/HUAWEIAGR-W09HN; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/12.9 SP-engine/2.28.0 baiduboxapp/12.9.0.11 (Baidu; P1 10) NABar/1.0",
+ "expect": {
+ "vendor": "Honor",
+ "model": "AGR-W09HN",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad X7 8 LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 10; KOB2-AL00HN; HMSCore 6.0.0.306) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 HuaweiBrowser/11.1.3.300 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "KOB2-AL00HN",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad X7 8 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; KOB2-W09HN; HMSCore 6.1.0.314) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/12.0.0.301 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "KOB2-W09HN",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad X8 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 12; AGM-W09HN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "AGM-W09HN",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad X9 11.5 LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 13; ELN-L09 Build/HONORELN-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.86 Mobile Safari/537.36[FBAN/EMA;FBLC/zh_CN;FBAV/432.0.0.9.110;FBCX/modulariab;]",
+ "expect": {
+ "vendor": "Honor",
+ "model": "ELN-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Honor Pad X9 11.5 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 13; ELN-W09 Build/HONORELN-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.86 Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "ELN-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei Honor 6A",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; DLI-L22 Build/HONORDLI-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/252.0.0.22.355;]",
+ "expect": {
+ "vendor": "Honor",
+ "model": "DLI-L22",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Honor 7",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; PLK-L01 Build/HONORPLK-L01; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "PLK-L01",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei 10 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 9; HRY-LX1 Build/HONORHRY-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.91 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Honor",
+ "model": "HRY-LX1",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/htc.json b/test/data/ua/device/htc.json
new file mode 100644
index 000000000..51ff339ba
--- /dev/null
+++ b/test/data/ua/device/htc.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "HTC Desire 820",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; HTC Desire 820 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "HTC",
+ "model": "Desire 820",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HTC Evo Shift 4G",
+ "ua": "Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Sprint APA7373KT Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0",
+ "expect": {
+ "vendor": "Sprint",
+ "model": "APA7373KT",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HTC Nexus 9",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0; Nexus 9 Build/LRX21R) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Mobile Crosswalk/7.36.154.13 Safari/537.36",
+ "expect": {
+ "vendor": "HTC",
+ "model": "Nexus 9",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/huawei.json b/test/data/ua/device/huawei.json
new file mode 100644
index 000000000..670879e66
--- /dev/null
+++ b/test/data/ua/device/huawei.json
@@ -0,0 +1,947 @@
+[
+ {
+ "desc": "Huawei Honor",
+ "ua": "Mozilla/5.0 (Linux; U; Android 2.3; xx-xx; U8860 Build/HuaweiU8860) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "U8860",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Honor 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L41) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YAL-L41",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Honor 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YAL-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YAL-AL10",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nexus 6P",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MTC19V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "Nexus 6P",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P10",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; VTR-L09 Build/HUAWEIVTR-L09; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "VTR-L09",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Y3II",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.1; xx-xx; HUAWEI LUA-L03 Build/HUAWEILUA-L03) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LUA-L03",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad C5 8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; MON-AL19B Build/HUAWEIMON-AL19; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.83 Mobile Safari/537.36 T7/11.7 baiduboxapp/11.7.0.10 (Baidu; P1 7.0)",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "MON-AL19B",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M2 10.1",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; HUAWEI M2-A01L Build/HUAWEIM2-A01L; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/95.0.4638.74 Safari/537.36[FBAN/EMA;FBLC/fr_FR;FBAV/421.0.0.14.100;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "M2-A01L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M3",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0; en-US; BTV-DL09 Build/HUAWEIBEETHOVEN-DL09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.5.0.1015 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BTV-DL09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M3 8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; HUAWEI BTV-W09 Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.96 Mobile Safari/537.36 AlohaBrowser/3.1.1",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BTV-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M3 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; CPN-L09 Build/HUAWEICPN-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36[FBAN/EMA;FBLC/ru_RU;FBAV/233.0.0.12.118;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "CPN-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M3 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; CPN-W09 Build/HUAWEICPN-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/374.0.0.20.109;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "CPN-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M3 Lite 10",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; BAH-L09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M5 10.8",
+ "ua": "Mozilla/5.0 (Linux; Android 9; CMR-W09 Build/HUAWEICMR-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.102 Safari/537.36 Line/14.18.1/IAB",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "CMR-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M5 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; BAH2-W19 Build/HUAWEIBAH2-W19; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH2-W19",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M5 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 9; JDN2-W09 Build/HUAWEIJDN2-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/90.0.4430.210 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/318.0.0.39.154;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "JDN2-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M5 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 9; JDN2-AL50 Build/HUAWEIJDN2-AL50; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/12.13.0 SP-engine/2.29.0 matrixstyle/0 lite baiduboxapp/5.8.0.10 (Baidu; P1 9) NABar/1.",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "JDN2-AL50",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M5 8.4",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SHT-W09 Build/HUAWEISHT-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "SHT-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M5",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SHT-AL09 Build/HUAWEISHT-AL09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "SHT-AL09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M6 10.8",
+ "ua": "Mozilla/5.0 (Linux; Android 14; SCM-W09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6612.143 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "SCM-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad M6 8.4",
+ "ua": "Mozilla/5.0 (Linux; Android 9; VRD-W09; HMSCore 6.14.0.321; GMSCore 22.26.15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "VRD-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T5",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; AGS2-L09 Build/HUAWEIAGS2-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/84.0.4147.125 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS2-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; en-US; AGR-L09 Build/HUAWEIAGR-L09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.3.8.1305 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGR-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGR-W09 Build/HUAWEIAGR-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGR-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10s",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGS3-W09 Build/HUAWEIAGS3-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS3-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T 8.0",
+ "ua": "Mozilla/5.0 (Linux; Android 10; KOB2-L09 Build/HUAWEIKOB2-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.105 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/396.0.0.21.104;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "KOB2-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T 8.0",
+ "ua": "Mozilla/5.0 (Linux; Android 10; KOB2-W09 Build/HUAWEIKOB2-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 Mobile Safari/537.36 HuaweiBrowser/15.0.4.312 HMSCore/6.14.0.301",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "KOB2-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T1 10",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.4; T1-A21w Build/HuaweiMediaPad) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Safari/537.36 SputnikBrowser/1.2.8.161",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "T1-A21w",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T1 10",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; T1-A23L Build/HuaweiMediaPad; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/43.0.2357.121 Mobile Safari/537.36 BingWeb/6.9.10",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "T1-A23L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T1 10",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; T1-A21L Build/HuaweiMediaPad) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "T1-A21L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T1 7",
+ "ua": "Mozilla/5.0 (Linux; 4.4.2; T1-701u) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "T1-701u",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T1 8",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9.0; MediaPad T1 8.0 Build/HuaweiMediaPad) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30 OPR/28.0.2254.119224",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "MediaPad T1 8.0",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10 9.7",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; en-US; AGRK-L09 Build/HUAWEIAGRK-L09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.6.0.1315 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGRK-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10 9.7 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGRK-W09; HMSCore 6.14.0.321) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGRK-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10s 10.1 LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGS3K-L09 Build/HUAWEIAGS3K-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.105 Safari/537.36 [FB_IAB/FB4A;FBAV/362.0.0.27.109;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS3K-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T10s 10.1 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGS3K-W09; HMSCore 6.14.0.321) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS3K-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T2 10.0 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; 605HW Build/HuaweiMediaPad; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/102.0.5005.78 Safari/537.36 [FB_IAB/FB4A;FBAV/436.0.0.35.101;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "605HW",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T2 7.0 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; BGO-DL09 Build/HuaweiBAGGIO; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/106.0.5249.126 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/407.0.0.30.97;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BGO-DL09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T3 10",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; AGS-W09 Build/HUAWEIAGS-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.90 Safari/537.36 GSA/10.83.10.21.arm64",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T3 7",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; BG2-U03 Build/HUAWEIBG2-U03; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/84.0.4147.111 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BG2-U03",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T3 8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; KOB-W09 Build/HUAWEIKOB-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Safari/537.36 [FB_IAB/Orca-Android;FBAV/354.0.0.10.113;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "KOB-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad T5 10",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; AGS2-W09 Build/HUAWEIAGS2-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36 Flipboard/4.3.31/5486,4.3.31.5486",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS2-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "HUAWEI MediaPad X2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0; GEM-703L Build/HUAWEIGEM-703L; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/043906 Mobile Safari/537.36 MicroMessenger/6.6.3.1260(0x26060339) NetType/WIFI Language/zh_",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "GEM-703L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4",
+ "ua": "Mozilla/5.0 (Linux; Android 10; HarmonyOS; BAH3-W09; HMSCore 6.14.0.322) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH3-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4",
+ "ua": "Mozilla/5.0 (Linux; Android 10; HarmonyOS; BAH3-L09; HMSCore 6.14.0.322) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH3-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; BAH3-W59 Build/HUAWEIBAH3-W59; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.105 Safari/537.36HiSearch/22.0.6.315",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH3-W59",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4 (2022)",
+ "ua": "Mozilla/5.0 (Linux; Android 10; BAH4-L09 Build/HUAWEIBAH4-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.105 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH4-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4 (2022) WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; HarmonyOS; BAH4-W09; HMSCore 6.14.0.322) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BAH4-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4 SE",
+ "ua": "Mozilla/5.0 (Linux; Android 12; AGS5-L09 Build/HUAWEIAGS5-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.88 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS5-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 10.4 SE WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 12; AGS5-W09 Build/HUAWEIAGS5-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.88 Safari/537.36 [FB_IAB/FB4A;FBAV/480.0.0.54.88;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS5-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 11 (2023) WiFi",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; zh-Hans-CN; DBR-W10 Build/HUAWEIDBR-W10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 Quark/6.9.6.501 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "DBR-W10",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 11 WiFi",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; zh-cn; DBY-W09 Build/HUAWEIDBY-W09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/109.0.5414.86 MQQBrowser/14.6 Mobile Safari/537.36 COVC/046801",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "DBY-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 11.5 Air WiFi",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; zh-Hans-CN; DBY2-W00 Build/HUAWEIDBY2-W00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 Quark/7.3.8.663 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "DBY2-W00",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 11.5 LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 12; HarmonyOS; BTK-AL09; HMSCore 6.14.0.322) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BTK-AL09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 11.5 S WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 12; HarmonyOS; TGR-W09; HMSCore 6.14.0.322; GMSCore 0.3.3.1.240913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 HuaweiBrowser/14.0.2.317 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "TGR-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad 11.5 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 12; HarmonyOS; BTK-W09; HMSCore 6.14.0.322; GMSCore 214816056) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BTK-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad C5 8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; MON-W19 Build/HUAWEIMON-W19; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36 [Pinterest/Android]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "MON-W19",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad Pro 11",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 12; GOT-AL09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 YaBrowser/23.5.5.60.01 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "GOT-AL09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad Pro 11 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 12; GOT-W09 Build/HUAWEIGOT-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/97.0.4692.98 Safari/537.36 T7/13.19 BDOS/1.0 (HarmonyOS 3.0.0) SP-engine/2.57.0 baiduboxapp/13.19.0.12 (Baidu; P1 12) NABar/1.0",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "GOT-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad Pro 12.6 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; WGR-W09 Build/HUAWEIWGR-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.105 Safari/537.36[FBAN/EMA;FBLC/en_US;FBAV/412.0.0.8.106;]",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "WGR-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad SE 11 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; HarmonyOS; AGS6-W09; HMSCore 6.12.2.309) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 HuaweiBrowser/11.1.5.315 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS6-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad Pro 13.2",
+ "ua": "Mozilla/5.0 (Linux; Android 12; HarmonyOS; PCE-W29; HMSCore 6.14.0.322) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/15.0.4.312 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "PCE-W29",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad T 10",
+ "ua": "Mozilla/5.0 (Linux; Android 10; AGR-L09; HMSCore 5.0.4.301) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 HuaweiBrowser/11.0.3.304 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGR-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad T10s",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; zh-cn; AGS3-AL00 Build/HUAWEIAGS3-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/11.4 Mobile Safari/537.36 COVC/045530",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS3-AL00",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad T10s WiFi",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; AGS3-W09 Build/HUAWEIAGS3-W09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.93 Safari/537.36 OPR/60.0.2254.59405",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AGS3-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei MatePad T8 8 LTE",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; KOB2K-L09 Build/HUAWEIKOB2K-L09; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Safari/537.36 OPR/83.0.2254.73002",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "KOB2K-L09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei M3",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; BTV-W09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BTV-W09",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Huawei Mate 10 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0; BLA-L29 Build/HUAWEIBLA-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3236.6 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BLA-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate X",
+ "ua": "Mozilla/5.0 (Linux; Android 9; TAH-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.111 Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "TAH-AN00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate X2",
+ "ua": "Mozilla/5.0 (Linux; Android 10; TET-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "TET-AN00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 X",
+ "ua": "Mozilla/5.0 (Linux; Android 9; EVR-L29 Build/HUAWEIEVR-L29; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "EVR-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LYA-L09) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LYA-L09",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LYA-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LYA-AL00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LYA-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LYA-AL10",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LYA-L0C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LYA-L0C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LYA-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LYA-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LYA-TL00) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LYA-TL00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 50 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 12; DCO-LX9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "DCO-LX9",
+ "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",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "ANE-LX1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P20",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; EML-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "EML-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P20 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; CLT-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "CLT-L29",
+ "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",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "ELE-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P30 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; VOG-L29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "VOG-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P40",
+ "ua": "Mozilla/5.0 (Linux; Android 10; ANA-AN00 Build/HUAWEIANA-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/11.26 SP-engine/2.22.0 baiduboxapp/11.26.0.10 (Baidu; P1 10) NABar/1.0",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "ANA-AN00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P40 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; ELS-AN00 Build/HUAWEIELS-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36 mailapp/6.0.0",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "ELS-AN00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei 30 Pro+",
+ "ua": "Mozilla/5.0 (Linux; Android 10; EBG-AN10 Build/HUAWEIEBG-AN10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.86 Mobile Safari/537.36 EdgA/42.0.0.2741",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "EBG-AN10",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei 30S",
+ "ua": "Mozilla/5.0 (Linux; Android 10; CDY-AN90 Build/HUAWEICDY-AN90; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36 mailapp/5.8.0",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "CDY-AN90",
+ "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",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YAL-L21",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nova 5T",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L61) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YAL-L61",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nova 5T",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L71) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YAL-L71",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nova 5T",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YAL-L61D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YAL-L61D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nova 5T",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YALE-L61A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YALE-L61A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nova 5T",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YALE-L61D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YALE-L61D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Nova 5T",
+ "ua": "Mozilla/5.0 (Linux; Android 10; YALE-L71A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "YALE-L71A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Enjoy10e",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 10; MED-AL00 Build/HUAWEIMED-AL00)",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "MED-AL00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Y7 2018",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; LDN-L01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.62 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "LDN-L01",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Honor 8X",
+ "ua": "Mozilla/5.0 (Linux; Android 9; JSN-L21) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "JSN-L21",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Y6 2019",
+ "ua": "Mozilla/5.0 (Linux; Android 9; MRD-LX1N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "MRD-LX1N",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Y9 2019",
+ "ua": "Mozilla/5.0 (Linux; Android 9; JKM-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "JKM-LX2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Y5",
+ "ua": "Mozilla/5.0 (Linux; Android 9; AMN-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "AMN-LX3",
+ "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",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "ART-L29",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Mate 20 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; SNE-LX1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "SNE-LX1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei P10 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX1A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "WAS-LX1A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Y5 Lite 2018",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; DRA-LX5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "DRA-LX5",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Huawei Honor 8C",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; BKK-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Huawei",
+ "model": "BKK-LX2",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/imo.json b/test/data/ua/device/imo.json
new file mode 100644
index 000000000..e794a801b
--- /dev/null
+++ b/test/data/ua/device/imo.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc": "IMO FEEL A2",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; IMO FEEL A2 Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/49.0.2623.105 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "IMO",
+ "model": "FEEL A2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "IMO Q2",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; IMO Q2 Build/LMY47D; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/65.0.3325.109 Mobile Safari/537.36 GSA/7.22.24.21.arm",
+ "expect": {
+ "vendor": "IMO",
+ "model": "Q2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "IMO S2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; IMO S2 Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.162 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "IMO",
+ "model": "S2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "IMO Tab X9",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.3; id-id; IMO TAB X9 Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
+ "expect": {
+ "vendor": "IMO",
+ "model": "TAB X9",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/infinix.json b/test/data/ua/device/infinix.json
new file mode 100644
index 000000000..3474003b9
--- /dev/null
+++ b/test/data/ua/device/infinix.json
@@ -0,0 +1,83 @@
+[
+ {
+ "desc": "Infinix Hot 4",
+ "ua": "Mozilla/5.0 (Linux; U; Android 7.0; en-us; Infinix HOT 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36 PHX/17.9",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "HOT 4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix Hot 4 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Infinix_X556_LTE Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/390.0.0.27.105;]",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X556_LTE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix Hot 7 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Infinix X625C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X625C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix Hot 10T",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Infinix X689C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X689C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix Hot 11s",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Infinix X6812 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/111.0.5563.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X6812",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix Note 3",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Infinix_X601_LTE Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/81.0.4044.111 Mobile Safari/537.36 GSA/11.5.9.21.arm64",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X601_LTE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix Smart 5",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Infinix X657C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X657C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Infinix XPad",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Infinix X1101B Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.99 Safari/537.36 [FB_IAB/FB4A;FBAV/489.0.0.66.81;IABMV/1;]",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X1101B",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Infinix Zero 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Infinix X6815B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Infinix",
+ "model": "X6815B",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/itel.json b/test/data/ua/device/itel.json
new file mode 100644
index 000000000..109e6e0b5
--- /dev/null
+++ b/test/data/ua/device/itel.json
@@ -0,0 +1,56 @@
+[
+ {
+ "desc": "itel A25",
+ "ua": "Mozilla/5.0 (Linux; Android 9; itel L5002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.130 Mobile Safari/537.36 OPR/63.3.3216.58675",
+ "expect": {
+ "vendor": "itel",
+ "model": "L5002",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "itel A50",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; itel A667L Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.103 Mobile Safari/537.36 OPR/83.1.2254.73239",
+ "expect": {
+ "vendor": "itel",
+ "model": "A667L",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "itel KidPad 1",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Itel W7001) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.101 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "itel",
+ "model": "W7001",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "itel Pad One",
+ "ua": "Mozilla/5.0 (Linux; Android 12; itel P10001L Build/SP1A.210812.016) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.172 Safari/537.36",
+ "expect": {
+ "vendor": "itel",
+ "model": "P10001L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "itel RS4",
+ "ua": "Mozilla/5.0 (Linux; Android 13; itel S666LN Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.165 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/468.1.0.56.78;]",
+ "expect": {
+ "vendor": "itel",
+ "model": "S666LN",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "itel Vision 2S",
+ "ua": "Mozilla/5.0 (Linux; Android 11; itel P651L Build/RP1A.201005.001) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.76 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "itel",
+ "model": "P651L",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/jolla.json b/test/data/ua/device/jolla.json
new file mode 100644
index 000000000..622fea01f
--- /dev/null
+++ b/test/data/ua/device/jolla.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc": "Jolla",
+ "ua": "Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0",
+ "expect": {
+ "vendor": "Jolla",
+ "model": "undefined",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/kobo.json b/test/data/ua/device/kobo.json
new file mode 100644
index 000000000..6648a9a59
--- /dev/null
+++ b/test/data/ua/device/kobo.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc": "Kobo eReader",
+ "ua": "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Kobo eReader Safari/538.1",
+ "expect": {
+ "vendor": "Kobo",
+ "model": "eReader",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Kobo Touch",
+ "ua": "Mozilla/5.0 (Linux; U; Android 2.0; en-us;) AppleWebKit/538.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/538.1 (Kobo Touch 0377/4.20.14622)",
+ "expect": {
+ "vendor": "Kobo",
+ "model": "Touch",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/lava.json b/test/data/ua/device/lava.json
new file mode 100644
index 000000000..5279f2cf5
--- /dev/null
+++ b/test/data/ua/device/lava.json
@@ -0,0 +1,110 @@
+[
+ {
+ "desc": "Lava Agni 2 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 14; LAVA LXX504 Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.46 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/499.0.0.31.60;IABMV/1;]",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LXX504",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Agni 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; LAVA LXX501 Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/122.0.6261.119 Mobile Safari/537.36[FBAN/EMA;FBLC/hi_IN;FBAV/396.0.0.9.115;]",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LXX501",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Blaze",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; en-US; LAVA Blaze Build/SP1A.210812.016) 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": "LAVA",
+ "model": "Blaze",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Blaze 2",
+ "ua": "Mozilla/5.0 (Linux; Android 13; LAVA LZX409 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.97 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/497.0.0.47.36;IABMV/1;]",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LZX409",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Blaze 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; LAVA LXX503) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LXX503",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Blaze Curve",
+ "ua": "Mozilla/5.0 (Linux; Android 14; LAVA LXX505 Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.105 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/505.0.0.57.45;IABMV/1;]",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LXX505",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Blaze Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; LAVA LZX404 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.50 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/503.0.0.69.76;IABMV/1;]",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LZX404",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Iris 46",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LAVA LH9950) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/21.0 Chrome/110.0.5481.154 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LH9950",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Iris 54",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9; LAVA LH9931 Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36 OPR/39.1.2254.136708",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LH9931",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava Storm 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 13; LAVA LXX508 Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.100 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LXX508",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava V7 Prime",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LAVA LE9940) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LE9940",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lava V7s Prime",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9; LAVA LE9940_W Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36 OPR/43.2.2254.140294",
+ "expect": {
+ "vendor": "LAVA",
+ "model": "LE9940_W",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/lenovo.json b/test/data/ua/device/lenovo.json
new file mode 100644
index 000000000..526a3b50c
--- /dev/null
+++ b/test/data/ua/device/lenovo.json
@@ -0,0 +1,1100 @@
+[
+ {
+ "desc": "Lenovo A7",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9; en-US; Lenovo L19111 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/13.2.8.1301 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L19111",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo A8",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Lenovo L10041) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.73 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L10041",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo dtab Compact 42A",
+ "ua": "Mozilla/5.0 (Linux; Android 12; d-42A Build/SKQ1.220201.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/102.0.5005.125 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "d-42A",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo IdeaTab A7-50",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Lenovo A3500-HV Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "A3500-HV",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo IdeaTab A2109A",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; ru-ru; A2109A Build/JDQ39; CyanogenMod-10.1) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "A2109A",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo IdeaTab S6000",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; S6000 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "S6000",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo IdeaTab S6000",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; IdeaTab S6000-H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 YaBrowser/18.11.1.1011.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "IdeaTab S6000-H",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo K5 Pro",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9;zh-cn; Lenovo L38041 Build/PKQ1.190127.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/109.0.5414.117 MobileLenovoBrowser/9.1.3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L38041",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo K9",
+ "ua": "Mozilla/5.0 (Linux; U; Android 8.1.0; en-US; Lenovo L38043 Build/O11019) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 UCBrowser/11.4.8.1012 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L38043",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo K10 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo L39051) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.66 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L39051",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo K12",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Lenovo XT2081-4 Build/QCZ30.30-Q3-45-17; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/409.0.0.27.106;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "XT2081-4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo K12",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; Lenovo K12 Build/QOGS30.569-83-18; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.130 Mobile Safari/537.36 OPR/69.0.2254.66073",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "K12",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo Legion 2 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Lenovo L70081 Build/RKQ1.201112.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.58 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L70081",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo Legion Y90",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12;en-us; Lenovo L71061/SKQ1.211113.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.132 MobileLenovoBrowser/8.6.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L71061",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo Legion Y700",
+ "ua": "Mozilla/5.0 (Linux; U; Android 13;zh-cn; Lenovo TB-9707F Build/TKQ1.221013.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/109.0.5414.117 MobileLenovoBrowser/2.1.7 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-9707F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Legion Y700",
+ "ua": "Mozilla/5.0 (Linux; Android 12; TB320FC) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB320FC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Moto Tab",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; TB-X704A Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X704A",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Phone",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Lenovo PB2-650M Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.105 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/311.0.0.44.117;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "PB2-650M",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo S5 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo L58041) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L58041",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo Smart Tab M8",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 10; Lenovo TB-8505XS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.85 YaBrowser/21.11.7.71.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8505XS",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Smart Tab M8",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 10; Lenovo TB-8505FS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.166 YaBrowser/21.8.4.111.00 (beta) SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8505FS",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 2",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.1; Lenovo TAB 2 A7-30HC Build/LRX21M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TAB 2 A7-30HC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 2 A7",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0.99; Lenovo TAB 2 A7-30DC Build/LRX21M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.141 Safari/537.36 OPR/45.1.2246.125351",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TAB 2 A7-30DC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 2 A10",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.0.1; Lenovo TAB 2 A10-70L Build/LRX21M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Safari/537.36 OPR/29.0.2254.120398",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TAB 2 A10-70L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 2 A10-30",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; TB2-X30F Build/LenovoTB2-X30F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Mobile Safari/537.36 EdgA/90.0.818.49",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB2-X30F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 7",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Lenovo TB3-730X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB3-730X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 7 Essential",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; Lenovo TB3-710I Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/85.0.4183.127 Safari/537.36 GSA/5.4.28.19.arm",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB3-710I",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 7 Plus",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; en-US; Lenovo TB-7703X Build/S100) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.2.5.1102 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7703X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 8 Dual",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; 602LV Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/93.0.4577.82 Safari/537.36 GSA/12.36.22.23.arm64",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "602LV",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 8 Plus",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; Lenovo TB-8703F Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.8 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8703F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 8 Plus",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; Lenovo TB-8703X Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/65.0.3325.109 Safari/537.36 OPR/33.0.2254.125672",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8703X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 10 Business",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0; Lenovo TB3-X70F Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Safari/537.36 OPR/32.0.2254.123747",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB3-X70F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 10 Plus",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0; Lenovo TB3-X70L Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/86.0.4240.185 Safari/537.36 OPR/52.1.2254.54298",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB3-X70L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 3 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Lenovo YT3-X90F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-X90F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; Lenovo TB-X304F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X304F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Lenovo TAB 2 A7-30HC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TAB 2 A7-30HC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 8",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; Lenovo TB-8504F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36 OPR/64.2.3282.60128",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8504F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 8",
+ "ua": "Mozilla/5.0 (Linux; U; Android 8.1.0; Lenovo TB-8504X Build/OPM1.171019.019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36 OPR/52.2.2254.54723",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8504X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 8 Plus",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 8.1.0; Lenovo TB-8704F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 YaApp_Android/21.21.0/apad YaSearchBrowser/21.21.0/apad BroPP/1.0 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8704F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 8 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; TB-8704V Build/OPM1.171019.019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/104.0.5112.97 Safari/537.36 [FB_IAB/FB4A;FBAV/380.0.0.29.109;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8704V",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 8 Plus",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 8.1.0; Lenovo TB-8704X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 YaBrowser/19.10.4.187.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8704X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 8 REL",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; Lenovo TB-8X04F Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.100 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8X04F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 10",
+ "ua": "Mozilla/5.0 (Linux; U; Android 8.1.0; Lenovo TB-X304L Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.149 Safari/537.36 OPR/47.0.2254.146760",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X304L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 10 Plus",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 7.1.1; Lenovo TB-X704L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 YaBrowser/20.12.4.100.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X704L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 4 10 Plus",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 7.1.1; Lenovo TB-X704F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 YaBrowser/20.8.5.97.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X704F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 6",
+ "ua": "Mozilla/5.0 (Linux; Android 11; A101LV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.61 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "A101LV",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 7",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; Lenovo TB-7504X Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/119.0.6045.193 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/436.0.0.35.101;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7504X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 7 Essential",
+ "ua": "Mozilla/5.0 (Linux; U; Android 7.0; Lenovo TB-7304I Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Safari/537.36 OPR/54.0.2254.56148",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7304I",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 7 Essential",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; Lenovo TB-7304X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7304X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab 10 10.1",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Lenovo TB-X103F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X103F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab E7",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 8.1.0; Lenovo TB-7104I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 BroPP/1.0 SA/3 Mobile Safari/537.36 YandexSearch/7.52/apad YandexSearchBrowser/7.52",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7104I",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab E8",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 7.0; Lenovo TB-8304F1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.143 YaBrowser/19.7.4.97.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8304F1",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab K10",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Lenovo TB-X6C6X Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X6C6X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab K10",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Lenovo TB-X6C6F Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.83 Safari/537.36 [FB_IAB/FB4A;FBAV/488.0.0.78.79;IABMV/1;] FBNV/5",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X6C6F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab K11",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 10; Lenovo TB-J606N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 YaApp_Android/22.31.1/apad YaSearchBrowser/22.31.1/apad BroPP/1.0 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J606N",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab K11 Pro 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Lenovo TB-J607Z Build/SKQ1.211103.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.58 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J607Z",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M7",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo TB-7305X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36 OPR/63.3.3216.58675",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7305X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M7",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 9; Lenovo TB-7305I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 YaApp_Android/20.85.0/apad YaSearchBrowser/20.85.0/apad BroPP/1.0 SA/1 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7305I",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M7 (Gen 3)",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Lenovo TB-7306F Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.86 Safari/537.36 [FB_IAB/FB4A;FBAV/489.0.0.66.81;IABMV/1;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7306F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M7 (Gen 3)",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 11; Lenovo TB-7306X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 YaBrowser/20.12.5.127.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-7306X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M8",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Lenovo TB-8505X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8505X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M8",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo TB-8505F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.101 Mobile Safari/537.36 GSA/10.82.8.21.arm64",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8505F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M8 (Gen 3)",
+ "ua": "Mozilla/5.0 (Linux; U; Android 11; zh-TW; Lenovo TB-8506X Build/RP1A.200720.011) 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": "Lenovo",
+ "model": "TB-8506X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M8 (Gen 4)",
+ "ua": "Mozilla/5.0 (Linux; Android 13; TB300FU Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.86 Mobile Safari/537.36[FBAN/EMA;FBLC/en_US;FBAV/417.0.0.9.97;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB300FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M8 (Gen 4) (2024)",
+ "ua": "Mozilla/5.0 (Linux; Android 13; TB301FU Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.170 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/472.0.0.45.79;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB301FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M8 FHD",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo TB-8705X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.99 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-8705X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Linux; Android 12; TB310FU) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB310FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Linux; 13; TB310XU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB310XU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 9; Lenovo TB-X606F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 YaBrowser/20.9.4.99.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X606F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Android 14; Mobile; Lenovo TB-X505F; rv:131.0) Gecko/131.0 Firefox/131.0",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X505F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Lenovo TB-X505L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6554.180 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X505L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo TB-X605F Build/PKQ1.190319.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.101 Safari/537.36 [FB_IAB/FB4A;FBAV/298.0.0.46.116;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X605F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; Lenovo TB-X505X Build/QKQ1.191224.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/81.0.4044.138 Safari/537.36 OPR/52.2.2254.54723",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X505X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 (Gen 3) ",
+ "ua": "Mozilla/5.0 (Linux; Android 12; TB328XU Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.58 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB328XU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 (Gen 3) ",
+ "ua": "Mozilla/5.0 (Linux; Android 12; TB328FU Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.57 Safari/537.36 [FB_IAB/FB4A;FBAV/418.0.0.33.69;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB328FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 FHD",
+ "ua": "Mozilla/5.0 (Linux; Android 13; Lenovo TB-X605FC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6481.193 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X605FC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 FHD",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo TB-X605LC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X605LC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 FHD",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 9; Lenovo TB-X605L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 YaBrowser/20.12.0.141.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X605L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 FHD Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 14; Lenovo TB-X606F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6496.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X606F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 FHD Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo TB-X606FA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5813.205 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X606FA",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 FHD Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Lenovo TB-X606X Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.101 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X606X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 HD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; it-it; Lenovo TB-X306F Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36 PHX/6.2",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X306F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 HD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; it-it; Lenovo TB-X306F Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36 PHX/6.2",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X306F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M10 Plus (Gen 3)",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; Lenovo TB-X306X Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/90.0.4430.210 Safari/537.36 OPR/55.1.2254.56965",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X306X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab M11",
+ "ua": "Mozilla/5.0 (Linux; Android 14; TB330FU Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.60 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB330FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P10",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 9; Lenovo TB-X705L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 YaBrowser/21.3.3.160.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-X705L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; Lenovo TB-J606L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.2311.135 YaBrowser/21.11.5.121.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J606L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11",
+ "ua": "Mozilla/5.0 (Linux; U; Android 11; zh-cn; Lenovo TB-J606F Build/RKQ1.210303.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.1 Mobile Safari/537.36 COVC/045830",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J606F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Lenovo TB-J616X Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Safari/537.36 [FB_IAB/FB4A;FBAV/418.0.0.33.69;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J616X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11 (Gen 2)",
+ "ua": "Mozilla/5.0 (Linux; Android 14; TB350FU Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.46 Safari/537.36 [FB_IAB/FB4A;FBAV/490.0.0.63.82;IABMV/1;]",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB350FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Lenovo TB-J616F Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.9 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J616F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11 Pro",
+ "ua": "Mozilla/5.0 (Linux; U; Android 11; zh-CN; Lenovo TB-J706F Build/RKQ1.201112.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Quark/5.8.6.223 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J706F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Lenovo TB-J706L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 EdgA/103.0.1264.71",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J706L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P11 Pro (Gen 2)",
+ "ua": "Mozilla/5.0 (Linux; Android 13; TB132FU Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB132FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P12",
+ "ua": "Mozilla/5.0 (Linux; Android 14; TB370FU Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.106 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB370FU",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P12 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; Lenovo TB-Q706F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-Q706F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab P12 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; Lenovo TB-Q706Z Build/TKQ1.221013.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.103 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-Q706Z",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab QT K11 WiFi",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 12; Lenovo TB-J6C6F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.114 YaBrowser/22.9.3.82.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J6C6F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab QT K11 Pro WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Lenovo TB-J607F Build/RKQ1.201217.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.86 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "TB-J607F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab V7",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9; en-US; Lenovo PB-6505M Build/PKQ1) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.9.9.1155 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "PB-6505M",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Tab V7",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 9; Lenovo PB-6505Y) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 YaBrowser/20.9.3.85.00 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "PB-6505Y",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo X3 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Lenovo X3 Lite) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.88 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "X3 Lite",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Smart Tab",
+ "ua": "Mozilla/5.0 (Android 11; Mobile; Lenovo YT-X705X; rv:129.0) Gecko/129.0 Firefox/129.0",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT-X705X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Smart Tab",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 10; Lenovo YT-X705F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 YaBrowser/20.2.4.153.01 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT-X705F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Smart Tab",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Lenovo YT-X705L Build/PKQ1.181218.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.101 Safari/537.36 GSA/11.38.8.23.arm64",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT-X705L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; Lenovo YT3-X50L Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.149 Safari/537.36 OPR/46.0.2254.145391",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-X50L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; Lenovo YT3-X50L Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.149 Safari/537.36 OPR/46.0.2254.145391",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-X50L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Lenovo YT3-X50M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-X50M",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3 8",
+ "ua": "Mozilla/5.0(Linux; U; Android 5.1.1; pt-BR; Lenovo YT3-850F Build/LMY47V) AppleWebKit/537.36(KHTML, like Gecko) Version/4.0 Chrome/38.0.2125.102 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-850F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3 8",
+ "ua": "Mozilla/5.0(Linux; U; Android 5.1.1; lv-LV; Lenovo YT3-850L Build/LMY47V) AppleWebKit/537.36(KHTML, like Gecko) Version/4.0 Chrome/38.0.2125.102 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-850L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3 10",
+ "ua": "Mozilla/5.0(Linux; U; Android 5.1.1; vi-VN; Lenovo YT3-850M Build/LMY47V) AppleWebKit/537.36(KHTML, like Gecko) Version/4.0 Chrome/38.0.2125.102 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT3-850M",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; Lenovo YT-X703L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36 OPR/64.2.3282.60128",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT-X703L",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 3 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.2; YT-X703F Build/NJH47F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.141 Safari/537.36 OPR/45.0.2246.125120",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT-X703F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tab 11",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 12; Lenovo YT-J706X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.42 YaBrowser/24.1.1.42.01 (beta) Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "YT-J706X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 8",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4.2; ru-ru; Lenovo B6000; Android/4.4.2; Release/08.26.2015) AppleWebKit/534.30 (KHTML, like Gecko) Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B6000",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 8",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Lenovo B6000-H Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36 GSA/7.24.32.16.arm",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B6000-H",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 8",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; es-us; Lenovo B6000-F/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.2.2 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B6000-F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 10",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; ru-ru; Lenovo B8000-F Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B8000-F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 10",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; ru-ru; Lenovo B8000-H Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30 Mobile UCBrowser/3.4.3.532",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B8000-H",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 10 HD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-US; Lenovo B8080-H Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.0.0.1088 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B8080-H",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Yoga Tablet 10 HD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.3; ru-ru; Lenovo B8080-F/JLS36C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.3 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "B8080-F",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Lenovo Z6",
+ "ua": "Mozilla/5.0 (Linux; U; Android 9;zh-cn; Lenovo L78121 Build/PKQ1.190319.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/109.0.5414.117 MobileLenovoBrowser/9.1.3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Lenovo",
+ "model": "L78121",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/lg.json b/test/data/ua/device/lg.json
new file mode 100644
index 000000000..9f125d72b
--- /dev/null
+++ b/test/data/ua/device/lg.json
@@ -0,0 +1,209 @@
+[
+ {
+ "desc": "LG V40 ThinQ",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LM-V405) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-V405",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG K30",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; LM-X410.F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-X410.F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG K30",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LM-X410.FGN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-X410.FGN",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG K40",
+ "ua": "Mozilla/5.0 (Linux; Android 10; LM-X420) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.57 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-X420",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Stylo 4",
+ "ua": "Mozilla/5.0 (Linux; Android 10; LM-Q710(FGN)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.57 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "undefined",
+ "model": "LM-Q710(FGN)",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Stylo 5",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LM-Q720) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-Q720",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG G7 ThinQ",
+ "ua": "Mozilla/5.0 (Linux; Android 9; LM-G710VM Build/PKQ1.181105.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-G710VM",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG K20",
+ "ua": "Mozilla/5.0 (Android 13; Mobile; LG-M255; rv:111.0) Gecko/111.0 Firefox/111.0",
+ "expect": {
+ "vendor": "LG",
+ "model": "M255",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG K500",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; LG-K500 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "K500",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Nexus 4",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.1; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
+ "expect": {
+ "vendor": "LG",
+ "model": "Nexus 4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Nexus 4",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; Google Nexus 4 - 4.3 - API 18 - 768x1280 Build/JLS36G) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "LG",
+ "model": "Nexus 4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Nexus 5",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
+ "expect": {
+ "vendor": "LG",
+ "model": "Nexus 5",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Wing",
+ "ua": "Mozilla/5.0 (Linux; Android 10; LM-F100N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.101 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LM-F100N",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "LG Smart TV",
+ "ua": "Mozilla/5.0 (DirectFB; U; Linux mips; en) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+) LG Browser (; LG NetCast.TV-2011)",
+ "expect": {
+ "vendor": "LG",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "LG Smart TV",
+ "ua": "Mozilla/5.0 (Linux; NetCast; U) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/53.0.2785 34 Safari/537.31 SmartTV/8.5",
+ "expect": {
+ "vendor": "LG",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "LG Smart TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen Safari/537.41 LG Browser/7.00.00(LGE; 42LB670V-ZA; 05.05.90; 1); webOS.TV-2014; LG NetCast.TV-2013 Compatible (LGE, 42LB670V-ZA, wireless)",
+ "expect": {
+ "vendor": "LG",
+ "model": "42LB670V-ZA",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "LG Smart TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chr0me/53.0.2785.34 Safari/537.36 LG Browser/8.00.00(LGE; 32LM627BPSB; 05.40.45; 1; DTV_W19R); webOS.TV-2019; LG NetCast.TV-2013 Compatible (LGE, 32LM627BPSB, wireless)",
+ "expect": {
+ "vendor": "LG",
+ "model": "32LM627BPSB",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "LG Smart TV",
+ "ua": "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 HbbTV/1.3.1 ( DRM; LGE; OLED55B7V_Z; WEBOS3.5 06.10.60; W3_M16P; ) FVC/2.0 (LGE; WEBOS3.5 ;)",
+ "expect": {
+ "vendor": "LG",
+ "model": "OLED55B7V_Z",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "LG Android TV",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-cn; LG Android TV Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
+ "expect": {
+ "vendor": "LG",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "LG VK Series Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.2; VK700 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.84 Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "VK700",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "LG LK Series Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.1; LGLK430 Build/LRX21Y) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/38.0.2125.102 Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "LK430",
+ "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",
+ "expect": {
+ "vendor": "LG",
+ "model": "Watch Urbane",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "LG G Watch R",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; G Watch R Build/LCA44B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Crosswalk/15.44.384.12 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "LG",
+ "model": "G Watch R",
+ "type": "wearable"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/meizu.json b/test/data/ua/device/meizu.json
new file mode 100644
index 000000000..e18fdf080
--- /dev/null
+++ b/test/data/ua/device/meizu.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc": "Meizu M5 Note",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; M5 Note Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043024 Safari/537.36 MicroMessenger/6.5.7.1040 NetType/WIFI Language/zh_CN",
+ "expect": {
+ "vendor": "Meizu",
+ "model": "M5 Note",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Meizu M3S",
+ "ua": "Mozilla/5.0 (X11; Linux; Android 5.1; MZ-M3s Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrom/45.0.2454.94 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Meizu",
+ "model": "M3s",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/micromax.json b/test/data/ua/device/micromax.json
new file mode 100644
index 000000000..041642097
--- /dev/null
+++ b/test/data/ua/device/micromax.json
@@ -0,0 +1,30 @@
+[
+
+ {
+ "desc": "Micromax Bharat 2 Plus",
+ "ua": "Mozilla/5.0 (Linux; U; Android 7.0; en-US; Micromax Q402Plus Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.12.9.1226 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Micromax",
+ "model": "Q402Plus",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Micromax Canvas Infinity",
+ "ua": "Mozilla/5.0 (Linux; U; Android 7.1.2; en-US; Micromax HS2 Build/N2G47H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 UCBrowser/13.2.0.1296 (SpeedMode) U4/1.0 UCWEB/2.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Micromax",
+ "model": "HS2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Micromax In 1b",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; Micromax E7533 Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.101 Mobile Safari/537.36 OPR/54.0.2254.56148",
+ "expect": {
+ "vendor": "Micromax",
+ "model": "E7533",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/microsoft.json b/test/data/ua/device/microsoft.json
new file mode 100644
index 000000000..79c35111a
--- /dev/null
+++ b/test/data/ua/device/microsoft.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Microsoft Lumia 950",
+ "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586",
+ "expect": {
+ "vendor": "Microsoft",
+ "model": "Lumia 950",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Microsoft Surface Duo",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 10; Surface Duo Build/2020.1014.61)",
+ "expect": {
+ "vendor": "Microsoft",
+ "model": "Surface Duo",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Microsoft Lumia 950",
+ "ua": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586",
+ "expect": {
+ "vendor": "Microsoft",
+ "model": "Lumia 950",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xbox",
+ "ua": "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; Xbox)",
+ "expect": {
+ "vendor": "Microsoft",
+ "model": "Xbox",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Xbox One",
+ "ua": "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; Xbox; Xbox One)",
+ "expect": {
+ "vendor": "Microsoft",
+ "model": "Xbox One",
+ "type": "console"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/motorola.json b/test/data/ua/device/motorola.json
new file mode 100644
index 000000000..18d1e48a3
--- /dev/null
+++ b/test/data/ua/device/motorola.json
@@ -0,0 +1,299 @@
+[
+ {
+ "desc": "Motorola Defy XT",
+ "ua": "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; XT555C Build/V1.67D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT555C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Droid RAZR 4G",
+ "ua": "Mozilla/5.0 (Linux; U; Android 2.3; xx-xx; DROID RAZR 4G Build/6.5.1-73_DHD-11_M1-29) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "DROID RAZR 4G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Edge 30 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; XT2201-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2201-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Edge 30 Ultra",
+ "ua": "Mozilla/5.0 (Linux; Android 14; motorola edge 30 ultra Build/U1SQS34.52-21-1-13; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 M",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "edge 30 ultra",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Edge 40",
+ "ua": "Mozilla/5.0 (Linux; Android 14; motorola edge 40 Build/U1TL34.115-16-1-7; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.104 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "edge 40",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Edge X30",
+ "ua": "Mozilla/5.0 (Linux; Android 14; XT2201-2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.59 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2201-2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola RAZR 2019",
+ "ua": "Mozilla/5.0 (Linux; Android 9; motorola razr) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/11.1 Chrome/75.0.3770.143 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "razr",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto 360",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4; Moto 360 Build/KNX01S) AppleWebKit/537.36 (KHTML, like Gecko) WIB/0.9.8 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "Moto 360",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Motorola Moto E",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; Moto E (4) Build/NDQS26.69-64-11-7; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "Moto E (4)",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto E6s",
+ "ua": "Mozilla/5.0 (Linux; Android 9; MOTOROLA E6S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "E6S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto E7 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 10; moto e(7) plus Build/QPZS30.30-Q3-38-69-12; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.14 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto e(7) plus",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto E7 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 14; XT2081-1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Mobile Safari/537.36 OPR/102.0.4864.0",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2081-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto G 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 14; moto g 5G - 2023 Build/U1TPNS34.26-48-2-7; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto g 5G - 2023",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto G 5G (2022)",
+ "ua": "Mozilla/5.0 (Linux; Android 12; moto g 5G (2022) Build/S1SAS32.47-77-9; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/111.0.5563.116 Mobile Safari/537.36 [FB_IAB/Orca-Android;FBAV/402.0.0.11.101;]",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto g 5G (2022)",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto g(6) Play",
+ "ua": "Mozilla/5.0 (Linux; Android 9; moto g(6) play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto g(6) play",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto g(7) Supra",
+ "ua": "Mozilla/5.0 (Linux; Android 9; moto g(7) supra Build/PCOS29.114-134-2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/73.0.3683.90 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto g(7) supra",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto G10",
+ "ua": "Mozilla/5.0 (Linux; Android 12; XT2127-2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2127-2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto G24 Power",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; moto g24 power Build/UTAS34.82-97-1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.138 Mobile Safari/537.36 OPR/89.0.2254.76420",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto g24 power",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto G30",
+ "ua": "Mozilla/5.0 (Linux; Android 14; XT2129-3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2129-3",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto G54 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 14; moto g54 5G Build/U1TDS34.94-12-9-10-2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.122 Mobile Safari/537.36 OPX/2",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto g54 5G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto X",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.4; XT1097 Build/KXE21.187-38) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.109 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT1097",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto X",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2; xx-xx; XT1058 Build/13.9.0Q2.X-70-GHOST-ATT_LE-2) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT1058",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto X Force",
+ "ua": "Mozilla/5.0 (Linux; U; Android 7.0; XT1580 Build/NPKS25.200-12-9; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/65.0.3325.109 Mobile Safari/537.36 OPR/32.0.2254.123747",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT1580",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto S30",
+ "ua": "Mozilla/5.0 (Linux; Android 13; XT2243-2 Build/T1SJC33.51-19-7; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/97.0.4692.98 Mobile Safari/537.36 T7/13.30 SP-engine/2.53.0 matrixstyle/0 lite baiduboxapp/6.2.0.10 (Baidu; P1 13) NABar/1.0",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2243-2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto X30 Pro",
+ "ua": "Mozilla/5.0 (Linux; U; Android 13; zh-CN; XT2241-1 Build/T1SQ33.111-12-19) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/16.1.3.1264 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2241-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto Z3 Play",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Moto Z3 Play) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "Moto Z3 Play",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Moto Z4",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; moto z4 Build/QDF30.130-42-5-17; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 OPR/2.3.0",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "moto z4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Nexus 6",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.20 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "Nexus 6",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola One 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; motorola one 5G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "one 5G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola P30",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; XT1943-1 Build/OPM1.171019.019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/97.0.4692.98 Mobile Safari/537.36 T7/13.50 SP-engine/2.88.0 baiduboxapp/13.50.5.10 (Baidu; P1 8.1.0) NABar/1.0",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT1943-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Razr 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; motorola razr 5G Build/S2PS32.57-23-21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.97 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/497.0.0.47.36;IABMV/1;]",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "razr 5G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Razr 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; XT2071-4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "XT2071-4",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Motorola Razr 50 Ultra",
+ "ua": "Mozilla/5.0 (Linux; Android 14; motorola razr 50 ultra Build/U3UX34.56-29-2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.260 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Motorola",
+ "model": "razr 50 ultra",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/nintendo.json b/test/data/ua/device/nintendo.json
new file mode 100644
index 000000000..eb879b322
--- /dev/null
+++ b/test/data/ua/device/nintendo.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Nintendo Switch",
+ "ua": "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/606.4 (KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393",
+ "expect": {
+ "vendor": "Nintendo",
+ "model": "Switch",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Nintendo WiiU",
+ "ua": "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.4.2.9 NintendoBrowser/4.2.0.11146.EU",
+ "expect": {
+ "vendor": "Nintendo",
+ "model": "WiiU",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Nintendo Wii",
+ "ua": "Opera/9.10 (Nintendo Wii; U; ; 1621; en)",
+ "expect": {
+ "vendor": "Nintendo",
+ "model": "Wii",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Nintendo 3DS",
+ "ua": "Mozilla/5.0 (Nintendo 3DS; U; ; en) Version/1.7610.EU",
+ "expect": {
+ "vendor": "Nintendo",
+ "model": "3DS",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Nintendo 3DS",
+ "ua": "Mozilla/5.0 (New Nintendo 3DS like iPhone) AppleWebKit/536.30 (KHTML, like Gecko) NX/3.0.0.5.15 Mobile NintendoBrowser/1.3.10126.EU",
+ "expect": {
+ "vendor": "Nintendo",
+ "model": "3DS",
+ "type": "console"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/nokia.json b/test/data/ua/device/nokia.json
new file mode 100644
index 000000000..375cfcc3f
--- /dev/null
+++ b/test/data/ua/device/nokia.json
@@ -0,0 +1,119 @@
+[
+ {
+ "desc": "Nokia3xx",
+ "ua": "Nokia303/14.87 CLDC-1.1",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "303",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia 3.2",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Nokia 3.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "3.2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia 5800 XpressMusic",
+ "ua": "Mozilla/5.0 (SymbianOS/9.4; U; Series60/5.0 Nokia5800d-1/10.4.016; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "5800d-1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia 7",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Nokia 7.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "7.2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia 808 PureView",
+ "ua": "Mozilla/5.0 (Symbian; U; Nokia808 PureView; en-GB) AppleWebKit/534.3 (KHTML, like Gecko) Version/3.0 Mobile/1A543a Mobile Safari/534.3",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "808 PureView",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia 808 PureView",
+ "ua": "Mozilla/5.0 (Symbian/3; Series60/5.5 Nokia808PureView/113.010.1508; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/535.1 (KHTML, like Gecko) NokiaBrowser/8.3.2.21 Mobile Safari/535.1 3gpp-gba",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "808PureView",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia Lumia 630",
+ "ua": "UCWEB/2.0 (Windows; U; wds 8.10; en-IN; NOKIA; RM-978_1046) U2/1.0.0 UCBrowser/4.2.0.524 U2/1.0.0 Mobile",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "RM-978",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia N9",
+ "ua": "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "N9",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia N900",
+ "ua": "Mozilla/5.0 (Linux; Maemo 5.0; Nokia N900; Build/4.0.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "N900",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nokia T20",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Nokia T20) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "T20",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Nokia T20",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Nokia T20 Build/RP1A.201005.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/93.0.4577.62 Safari/537.36",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "T20",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Nokia T21",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 13; Nokia T21 Build/TP1A.220624.014)",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "T21",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Nokia 2720 Flip",
+ "ua": "Mozilla/5.0 (Mobile; Nokia_2720_Flip; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5.2",
+ "expect": {
+ "vendor": "Nokia",
+ "model": "2720 Flip",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/nothing.json b/test/data/ua/device/nothing.json
new file mode 100644
index 000000000..12b418d2c
--- /dev/null
+++ b/test/data/ua/device/nothing.json
@@ -0,0 +1,65 @@
+[
+ {
+ "desc": "Nothing 1",
+ "ua": "Mozilla/5.0 (Linux; Android 13; A063) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/22.0 Chrome/111.0.5563.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "A063",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nothing 2",
+ "ua": "Mozilla/5.0 (Linux; Android 14; A065 Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.53 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "A065",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nothing 2",
+ "ua": "Mozilla/5.0 (Linux; Android 15; AIN065 Build/AQ3A.240929.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.90 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/496.0.0.45.65;IABMV/1;] FBNV/500",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "AIN065",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nothing 2a",
+ "ua": "Mozilla/5.0 (Linux; Android 14; A142 Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.71 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "A142",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nothing 2a Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 14; A142P Build/UP1A.231005.007) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.107 Mobile Safari/537.36 OPX/2.6",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "A142P",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nothing 3a",
+ "ua": "Mozilla/5.0 (Linux; Android 15; A059 Build/AQ3A.241015.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.103 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "A059",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Nothing 3a Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 15; A059P Build/AQ3A.241015.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/136.0.7103.57 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/511.0.0.73.36;IABMV/1;]",
+ "expect": {
+ "vendor": "Nothing",
+ "model": "A059P",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/nvidia.json b/test/data/ua/device/nvidia.json
new file mode 100644
index 000000000..96a2afc98
--- /dev/null
+++ b/test/data/ua/device/nvidia.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Tegra Note 7",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; TegraNote-P1640 Build/LMY47D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Safari/537.36",
+ "expect": {
+ "vendor": "Nvidia",
+ "model": "TegraNote-P1640",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Nvidia Shield",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; SHIELD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.74 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Nvidia",
+ "model": "SHIELD",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Nvidia Shield Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SHIELD Tablet Build/LVY48E; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/45.0.2454.19 Safari/537.36",
+ "expect": {
+ "vendor": "Nvidia",
+ "model": "SHIELD Tablet",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Nvidia Shield Tablet K1",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; SHIELD Tablet K1 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Safari/537.36",
+ "expect": {
+ "vendor": "Nvidia",
+ "model": "SHIELD Tablet K1",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Nvidia Shield TV",
+ "ua": "Mozilla/5.0 (Linux; Android 11; SHIELD Android TV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36 ",
+ "expect": {
+ "vendor": "Nvidia",
+ "model": "SHIELD Android TV",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/oneplus.json b/test/data/ua/device/oneplus.json
new file mode 100644
index 000000000..a02c0f3c9
--- /dev/null
+++ b/test/data/ua/device/oneplus.json
@@ -0,0 +1,344 @@
+[
+ {
+ "desc": "OnePlus One",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.4; A0001 Build/KTU84Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.59 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "A0001",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus One",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; OnePlus One A0001 Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.117 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "A0001",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 2",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; ONE A2003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "A2003",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 3",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; ONEPLUS A3000 Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "A3000",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 6",
+ "ua": "Mozilla/5.0 (Linux; Android 9; ONEPLUS A6003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "A6003",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 6T",
+ "ua": "Mozilla/5.0 (Linux; Android 9; ONEPLUS A6010) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.96 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "A6010",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 7T Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.57 Mobile Safari/537.36 EdgA/110.0.1587.66",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "HD1913",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 8 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 10; OnePlus 8 Lite) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4741.47 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "8 Lite",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 8 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; IN2025) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.119 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "IN2025",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 8T",
+ "ua": "Mozilla/5.0 (Linux; Android 11; KB2005) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "KB2005",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 9",
+ "ua": "Mozilla/5.0 (Android 14; SDK 28; OnePlus 9) Gecko/128.0 Firefox/128.0",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "9",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 9",
+ "ua": "Mozilla/5.0 (Linux; Android 13; LE2113 Build/TP1A.220905.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36 Flipboard/4.3.14/5377,4.3.14.5377",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "LE2113",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 9 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; LE2123) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Mobile Safari/537.36 OPR/73.0.3832.69371",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "LE2123",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 9R",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; LE2100) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.85 YaApp_Android/21.114.1 YaSearchBrowser/21.114.1 BroPP/1.0 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "LE2100",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 9RT 5G",
+ "ua": "Mozilla/5.0 (Linux; U; Android 11; en-in; MT2111 Build/RKQ1.210614.002) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 GSA/13.14.15.23.arm64",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "MT2111",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 10 Pro",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 12; NE2213) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.148 YaBrowser/22.7.5.90.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "NE2213",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 10RT",
+ "ua": "Mozilla/5.0 (Linux; Android 13; CPH2413) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "CPH2413",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 11R",
+ "ua": "Mozilla/5.0 (Linux; Android 14; CPH2487) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "CPH2487",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus 13",
+ "ua": "Mozilla/5.0 (Linux; Android 15; CPH2653 Build/AP3A.240617.008; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/504.0.0.69.64;IABMV/1;] FBNV/5",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "CPH2653",
+ "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",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PGKM10",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 2",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; zh-CN; PHK110 Build/UKQ1.231108.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/17.3.6.1367 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PHK110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 2 Pro",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 13; PJA110) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 YaBrowser/23.1.0.284.00 (beta) SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PJA110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 2V",
+ "ua": "Mozilla/5.0 (Linux; U; Android 15; zh-cn; PHP110 Build/AP3A.240617.008) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/121.0.6167.71 MQQBrowser/16.3 Mobile Safari/537.36 COVC/047707",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PHP110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 3",
+ "ua": "Mozilla/5.0 (Linux; U; Android 15; zh-CN; PJE110 Build/TP1A.220905.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/123.0.6312.80 Quark/7.9.7.782 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PJE110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 3 Pro 5G",
+ "ua": "Mozilla/5.0 (Linux; U; Android 15; zh-CN; PJX110 Build/UKQ1.231108.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/123.0.6312.80 Quark/7.11.4.814 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PJX110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 3V",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; zh-cn; PJF110 Build/UP1A.231005.007) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/90.0.4430.61 Mobile Safari/537.36 HeyTapBrowser/40.8.33.1.2beta",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PJF110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace 5",
+ "ua": "Mozilla/5.0 (Linux; U; Android 15; zh-cn; PKG110 Build/UKQ1.231108.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.168 Mobile Safari/537.36 HeyTapBrowser/40.9.9.2",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PKG110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace Pro",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; zh-CN; PGP110 Build/UKQ1.230924.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/17.2.9.1360 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PGP110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Ace Racing Edition",
+ "ua": "Mozilla/5.0 (Linux; U; Android 13; zh-CN; PGZ110 Build/TP1A.220905.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/17.3.6.1367 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "PGZ110",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Nord CE 4",
+ "ua": "Mozilla/5.0 (Linux; Android 14; CPH2613) AppleWebKit/537.36 (KHTML, like Gecko) JioSphere/5.0.4 Chrome/119.0.6045.193 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "CPH2613",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Nord N10 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; BE2029) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.185 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "BE2029",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Nord N30 SE 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 14; CPH2605 Build/TP1A.220905.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.92 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/509.0.0.67.28;IABMV/1;]",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "CPH2605",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Nord N100",
+ "ua": "Mozilla/5.0 (Linux; Android 10; BE2015 Build/QKQ1.200719.002; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "BE2015",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Open",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 15; CPH2551 Build/AP3A.240617.008)",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "CPH2551",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OnePlus Pad Go 11.35",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 14; OPD2304) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.629 YaApp_Android/24.101/apad YaSearchBrowser/24.101/apad BroPP/1.0 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "OPD2304",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "OnePlus Pad 2 12.1 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 14; OPD2403 Build/UKQ1.231108.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "OPD2403",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "OnePlus Pad 11.61 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 14; OPD2203 Build/UKQ1.230924.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "OPD2203",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "OnePlus Watch 2",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 13; OPWWE231 Build/TWR7.231113.001.OPWWE231_11_A.117.240703)",
+ "expect": {
+ "vendor": "OnePlus",
+ "model": "OPWWE231",
+ "type": "wearable"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/oppo.json b/test/data/ua/device/oppo.json
new file mode 100644
index 000000000..ab63e2609
--- /dev/null
+++ b/test/data/ua/device/oppo.json
@@ -0,0 +1,164 @@
+[
+ {
+ "desc": "OPPO Pad",
+ "ua": "Mozilla/5.0 (Linux; U; Android 13; zh-CN; OPD2101 Build/TP1A.220905.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UCBrowser/16.3.9.1290 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "OPD2101",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "OPPO Neo",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-cn; R831T Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 OppoBrowser/3.3.2 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "R831T",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO R7s",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4.4; zh-cn; OPPO R7s Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 Chrome/37.0.0.0 MQQBrowser/7.1 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "R7s",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO A3s",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; CPH1803 Build/OPM1.171019.026; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "CPH1803",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO A12",
+ "ua": "Mozilla/5.0 (Linux; Android 9; CPH2083) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "CPH2083",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Reno",
+ "ua": "Mozilla/5.0 (Linux; Android 9; PCAT00 Build/PKQ1.190101.001; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "PCAT00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Reno3 Pro 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; PCLM50) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.117 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "PCLM50",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Reno4 SE",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; xx-xx; PEAM00 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "PEAM00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Reno4 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; PDPM00 Build/QKQ1.200216.002; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "PDPM00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Reno4 Pro 5G",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; xx-xx; PDNT00 Build/QKQ1.200216.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "PDNT00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Reno5 A",
+ "ua": "Mozilla/5.0 (Linux; Android 11; A101OP) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "A101OP",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Find X",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; PAFM00 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "PAFM00",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Find 7a",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.3; xx-xx; X9007 Build/JLS36C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "X9007",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO F5",
+ "ua": "ozilla/5.0 (Linux; Android 7.1.1; CPH1723) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "CPH1723",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "OPPO Watch 46mm",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; OW19W3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "OW19W3",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "OPPO Watch 41mm",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; OW19W2 Build/OPM1.171019.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "OW19W2",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "OPPO Watch 2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; OW20W1 Build/OPM1.171019.026.11_A.37.210713172937; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/61.0.3163.98 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "OW20W1",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "OPPO Watch X",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 13; OWWE231 Build/TWR7.231113.001.OWWE231_11_A.117.240703)",
+ "expect": {
+ "vendor": "OPPO",
+ "model": "OWWE231",
+ "type": "wearable"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/ouya.json b/test/data/ua/device/ouya.json
new file mode 100644
index 000000000..1fa74c0a1
--- /dev/null
+++ b/test/data/ua/device/ouya.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc": "Ouya",
+ "ua": "Mozilla/5.0 (Linux; Android 4.1.2; OUYA Console Build/JZO54L-OUYA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.84 Safari/537.36",
+ "expect": {
+ "vendor": "OUYA",
+ "model": "undefined",
+ "type": "console"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/panasonic.json b/test/data/ua/device/panasonic.json
new file mode 100644
index 000000000..568c21de2
--- /dev/null
+++ b/test/data/ua/device/panasonic.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Panasonic T31",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Panasonic T31 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.170 Mobile Safari/537.36 ",
+ "expect": {
+ "vendor": "Panasonic",
+ "model": "T31",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Panasonic TX-32CSW514 SmartTV",
+ "ua": "HbbTV/1.2.1 (;Panasonic;VIERA 2015;3.014;a001-003 4000-0000;)",
+ "expect": {
+ "vendor": "Panasonic",
+ "model": "VIERA 2015",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Panasonic TX-40FXW724 SmartTV",
+ "ua": "HbbTV/1.4.1 (+DRM;Panasonic;SmartTV2018mid;3.024;4301-0003 0002-0000;SmartTV2018;)",
+ "expect": {
+ "vendor": "Panasonic",
+ "model": "SmartTV2018mid",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Panasonic TX-43HXW904 SmartTV",
+ "ua": "HbbTV/1.5.1 (+DRM;Panasonic;SmartTV2020mid;3.326;4301-0003 0008-0000;com.panasonic.SmartTV2020mid;)",
+ "expect": {
+ "vendor": "Panasonic",
+ "model": "SmartTV2020mid",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Panasonic DMR-HST130 SAT receiver",
+ "ua": "HbbTV/1.1.1 (+PVR;Panasonic;DIGA WebKit M8658;3.420;;)",
+ "expect": {
+ "vendor": "Panasonic",
+ "model": "DIGA WebKit M8658",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/philips.json b/test/data/ua/device/philips.json
new file mode 100644
index 000000000..ebc5af032
--- /dev/null
+++ b/test/data/ua/device/philips.json
@@ -0,0 +1,83 @@
+[
+ {
+ "desc": "Philips S616",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; Philips S616 Build/LMY47D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Philips",
+ "model": "S616",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Philips W8510",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Philips W8510 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.68 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Philips",
+ "model": "W8510",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Philips SmartTV",
+ "ua": "Mozilla/5.0 (Linux; Android 11; PHILIPS 4k TV Build/RTXC.231010.082.A1; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.71 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "PHILIPS",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Philips SmartTV",
+ "ua": "Opera/9.80 HbbTV/1.1.1 (; Philips; ; ; ; ) NETTV/4.0.2; en) Version/11.60",
+ "expect": {
+ "vendor": "Philips",
+ "model": "",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Philips 32PFL6606K/02 SmartTV (2011)",
+ "ua": "Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.1.0; en) Presto/2.6.33 Version/10.70",
+ "expect": {
+ "vendor": "Philips",
+ "model": "",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Philips 32PFL6606K/02 SmartTV (2013)",
+ "ua": "Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.1.0; en) Presto/2.6.33 Version/10.70",
+ "expect": {
+ "vendor": "Philips",
+ "model": "",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Philips 32PHS5301/12 SmartTV (2016)",
+ "ua": "Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36 OPR/29.0.1803.0 OMI/4.5.23.37.MOT2.13 HbbTV/1.2.1 (;Philips;32PHS5301/12;;_TV_MT5800;) Firmware/TPM161E_012.002.045.001 en",
+ "expect": {
+ "vendor": "Philips",
+ "model": "32PHS5301/12",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Philips PH0M_EA_T32",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Philips FHD Android TV Build/QTG3.201102.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.186 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Philips",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Philips PH3M_AL_T32",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Philips Google TV TA7 Build/RTM5.220609.199; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/111.0.5563.58 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Philips",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/pico.json b/test/data/ua/device/pico.json
new file mode 100644
index 000000000..029a7c153
--- /dev/null
+++ b/test/data/ua/device/pico.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Pico 4",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; PICO 4 OS5.8.2 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.38 Chrome/105.0.5195.68 VR Safari/537.36",
+ "expect": {
+ "vendor": "PICO",
+ "model": "4",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Pico 4",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; PICO 4 OS5.4.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.22 Chrome/105.0.5195.68 VR Safari/537.36 OculusBrowser/7.0",
+ "expect": {
+ "vendor": "PICO",
+ "model": "4",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Pico Neo3 Link",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; Pico Neo3 Link OS5.8.4.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.22 Chrome/105.0.5195.68 VR Safari/537.36",
+ "expect": {
+ "vendor": "Pico",
+ "model": "Neo3 Link",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Pico Neo 3",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; Pico Neo 3 OS5.12.2 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.48 Chrome/105.0.5195.68 VR Safari/537.36 OculusBrowser/7.0",
+ "expect": {
+ "vendor": "Pico",
+ "model": "Neo 3",
+ "type": "xr"
+ }
+ },
+ {
+ "desc": "Pico Neo 3 Pro",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; Pico Neo 3 Pro OS5.9.9.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.46 Chrome/105.0.5195.68 VR Safari/537.36 OculusBrowser/7.0",
+ "expect": {
+ "vendor": "Pico",
+ "model": "Neo 3 Pro",
+ "type": "xr"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/polytron.json b/test/data/ua/device/polytron.json
new file mode 100644
index 000000000..595c27f68
--- /dev/null
+++ b/test/data/ua/device/polytron.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc": "Polytron Prime 7 Pro",
+ "ua": "Mozilla/5.0 (Linux; U; Android 7.0; POLYTRON_P552 Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36 OPR/50.0.2254.149182",
+ "expect": {
+ "vendor": "POLYTRON",
+ "model": "P552",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Polytron Rocket T1",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.0; en-US; POLYTRON R2501 Build/LRX21M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/13.1.2.1293 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "POLYTRON",
+ "model": "R2501",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Polytron Rocket T6",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; POLYTRON R2509) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.92 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "POLYTRON",
+ "model": "R2509",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Polytron Zap 6 Posh",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.1; in-ID; POLYTRON_4G501 Build/LMY47D) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.0.1163 UCTurbo/1.9.9.900 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "POLYTRON",
+ "model": "4G501",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/realme.json b/test/data/ua/device/realme.json
new file mode 100644
index 000000000..c16d4d0ac
--- /dev/null
+++ b/test/data/ua/device/realme.json
@@ -0,0 +1,74 @@
+[
+ {
+ "desc": "Realme C1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; RMX1811 Build/OPM1.171019.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.126 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX1811",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme C2",
+ "ua": "Mozilla/5.0 (Linux; Android 9; RMX1941) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX1941",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme Narzo 20",
+ "ua": "Mozilla/5.0 (Linux; U; Android 10; xx-xx; RMX2193 Build/QP1A.190711.020) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX2193",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme 2 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 9; RMX1801) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX1801",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme 3 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; RMX1851) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX1851",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme 8",
+ "ua": "Mozilla/5.0 (Linux; Android 12; RMX3085) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX3085",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme 9 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 13; RMX3471) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX3471",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Realme GT Master",
+ "ua": "Mozilla/5.0 (Linux; Android 13; RMX3363) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Realme",
+ "model": "RMX3363",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/retroid.json b/test/data/ua/device/retroid.json
new file mode 100644
index 000000000..fc7a83daf
--- /dev/null
+++ b/test/data/ua/device/retroid.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc": "Retroid Pocket 4 Pro",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 13; Retroid Pocket 4 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.96 YaBrowser/24.4.3.96.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Retroid",
+ "model": "Pocket 4 Pro",
+ "type": "console"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/roku.json b/test/data/ua/device/roku.json
new file mode 100644
index 000000000..a042f29b3
--- /dev/null
+++ b/test/data/ua/device/roku.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "Roku",
+ "ua": "Mozilla/5.0 (Roku) AppleWebKit/537.36 (KHTML, like Gecko) Web/1.1 Safari/537.36",
+ "expect": {
+ "vendor": "Roku",
+ "model": "",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Roku",
+ "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 Roku/DVP-8.10 (468.10E04145A)",
+ "expect": {
+ "vendor": "Roku",
+ "model": "DVP-8.10",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Roku",
+ "ua": "Roku4640X/DVP-7.70 (297.70E04154A)",
+ "expect": {
+ "vendor": "Roku",
+ "model": "DVP-7.70",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/samsung.json b/test/data/ua/device/samsung.json
new file mode 100644
index 000000000..36b68db0c
--- /dev/null
+++ b/test/data/ua/device/samsung.json
@@ -0,0 +1,398 @@
+[
+ {
+ "desc": "Samsung Galaxy A21s",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-A217F) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/11.0 Chrome/75.0.3770.143 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-A217F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy A31",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SM-A315G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-A315G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy A50",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SM-A505F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.105 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-A505F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy A50s",
+ "ua": "Mozilla/5.0 (Linux; Android 11; SM-A507FN) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-A507FN",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy A52s",
+ "ua": "Mozilla/5.0 (Linux; Android 13; SM-A528B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-A528B",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy A80",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SM-A805F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.112 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-A805F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Fold",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-F900U Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.2 Chrome/67.0.3396.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-F900U",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Z Flip",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SM-F700N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-F700N",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Z Fold2",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SM-F916B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-F916B",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy S10E",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SM-G970F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-G970F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy S20 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SCG01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SCG01",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Note 10+",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SM-N976V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-N976V",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung SM-C5000",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; SM-C5000 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36 wkbrowser 4.1.35 3065",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-C5000",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung C8",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; SM-C7108) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-C7108",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Note 8",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; GT-N5100 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.141 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "GT-N5100",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung SM-T231",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-T231 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-T231",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab 6 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-P610) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/12.0 Chrome/79.0.3945.136 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-P610",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab A 9.7",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; SM-P550 Build/NMF26X; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.90 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-P550",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab A 10.1",
+ "ua": " Mozilla/5.0 (Linux; Android 10; SAMSUNG SM-T515) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/13.0 Chrome/83.0.4103.106 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-T515",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab S7",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SM-T870) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-T870",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab S8",
+ "ua": "Mozilla/5.0 (Linux; Android 12; SM-X706B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-X706B",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab S",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-T700 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-T700",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Tab Pro 10.1",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-T520 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-T520",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Watch",
+ "ua": "Mozilla/5.0 (Linux; Tizen 5.5; SAMSUNG SM-R805W) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/2.0 Chrome/69.0.3497.106 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-R805W",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Watch Active 2",
+ "ua": "Mozilla/5.0 (Linux; Tizen 5.5; SAMSUNG SM-R820) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/2.0 Chrome/69.0.3497.106 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-R820",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Watch4",
+ "ua": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-R875U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/2.2. Chrome/102.0.5005.125 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-R875U",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Watch5 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-R925U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/3.2. Chrome/111.0.5563.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-R925U",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Watch7",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 14; SM-L300 Build/AW2E.240318.016)",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-L300",
+ "type": "wearable"
+ }
+ },
+ {
+ "desc": "Samsung Note 10.1",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SM-P605) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-P605",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV2011",
+ "ua": "HbbTV/1.1.1 (;;;;;) Maple;2011",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SmartTV2011",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV2012",
+ "ua": "HbbTV/1.1.1 (;Samsung;SmartTV2012;;;) WebKit",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SmartTV2012",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV2014",
+ "ua": "HbbTV/1.1.1 (;Samsung;SmartTV2014;T-NT14UDEUC-1060.4;;) WebKit",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SmartTV2014",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV",
+ "ua": "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV",
+ "ua": "Mozilla/5.0 (SMART-TV; Linux; Tizen 8.0) AppleWebKit/537.36 (KHTML, like Gecko) Samsung Browser/7.0 Chrome/108.0.5359.1 TV Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Samsung SmartTV HBBTV",
+ "ua": "HbbTV/1.5.1 (+DRM;Samsung;SmartTV2021:UAU7000;T-KSU2EDEUC-1506.0;KantSU2e;urn:samsungtv:familyname:21_KANTSU2E_UHD_BASIC:2021;) Tizen/6.0 (+TVPLUS+SmartHubLink) Chrome/76 LaTivu_1.0.1_2021 RVID/17",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SmartTV2021:UAU7000",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Galaxy Nexus",
+ "ua": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "Galaxy Nexus",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy C9 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; SAMSUNG SM-C900F Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.2 Chrome/44.0.2403.133 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-C900F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy S5",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0; SM-G900F Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.78 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-G900F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy J7 Prime",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; SM-G610F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-G610F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy S6",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-G920I Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-G920I",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy S6 Edge",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; SM-G925I Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.135 Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-G925I",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Note 5 Chrome",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SM-N920C Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.91 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-N920C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Note 5 Samsung Browser",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SAMSUNG SM-N920C Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "SM-N920C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Samsung Galaxy Gear Live",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4; Gear Live Build/KMV78X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/19.77.34.5 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Samsung",
+ "model": "Gear Live",
+ "type": "wearable"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/sharp.json b/test/data/ua/device/sharp.json
new file mode 100644
index 000000000..32d53409f
--- /dev/null
+++ b/test/data/ua/device/sharp.json
@@ -0,0 +1,56 @@
+[
+ {
+ "desc": "Sharp AQUOS-TVX19B",
+ "ua": "Mozilla/5.0 (Linux; Android 9; AQUOS-TVX19B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sharp",
+ "model": "AQUOS-TVX19B",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Sharp Aquos B10",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; SH-A01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sharp",
+ "model": "SH-A01",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sharp Aquos L2",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; SH-L02 Build/S4045) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sharp",
+ "model": "SH-L02",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sharp Aquos L2",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; SH-L02) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sharp",
+ "model": "SH-L02",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sharp Aquos R2",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0; SHV42) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.92 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sharp",
+ "model": "SHV42",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Docomo SH-02M",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SH-02M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sharp",
+ "model": "SH-02M",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/smartfren.json b/test/data/ua/device/smartfren.json
new file mode 100644
index 000000000..5413bb70b
--- /dev/null
+++ b/test/data/ua/device/smartfren.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc": "Smartfren Andromax L",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Andromax B26D2H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Smartfren",
+ "model": "Andromax B26D2H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Smartfren Andromax G2",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Smartfren Andromax AD9A1H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Smartfren",
+ "model": "Andromax AD9A1H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Smartfren New Andromax I",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.1.2; id-id; New Andromax-i Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Smartfren",
+ "model": "New Andromax-i",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/sony.json b/test/data/ua/device/sony.json
new file mode 100644
index 000000000..d4a3c21cb
--- /dev/null
+++ b/test/data/ua/device/sony.json
@@ -0,0 +1,263 @@
+[
+ {
+ "desc": "SONY Xperia 1 III",
+ "ua": "Mozilla/5.0 (Linux; Android 11; A101SO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "A101SO",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony G8141 (Xperia XZ1)",
+ "ua": "Mozilla/5.0 (Linux; Android 9; SO-01K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "SO-01K",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony G8141 (Xperia XZ Premium)",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; G8141) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "G8141",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony C5303 (Xperia SP)",
+ "ua": "Mozilla/5.0 (Linux; Android 4.3; C5303 Build/12.1.A.1.205) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "C5303",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony SO-02F (Xperia Z1 F)",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; SO-02F Build/14.1.H.2.119) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "SO-02F",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony D6653 (Xperia Z3)",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4; D6653 Build/23.0.A.0.376) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.141 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "D6653",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia SOL25 (ZL2)",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4; SOL25 Build/17.1.1.C.1.64) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Sony",
+ "model": "SOL25",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia SP",
+ "ua": "Mozilla/5.0 (Linux; Android 4.3; C5302 Build/12.1.A.1.201) AppleWebkit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "C5302",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia L4",
+ "ua": "Mozilla/5.0 (Linux; Android 9; XQ-AD51) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-AD51",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia 1ii",
+ "ua": "Mozilla/5.0 (Linux; Android 10; XQ-AT51) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-AT51",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia 1ii",
+ "ua": "Mozilla/5.0 (Linux; Android 10; SOG01) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "SOG01",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia 1 VI",
+ "ua": "Mozilla/5.0 (Linux; Android 14; XQ-EC44) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-EC44",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia 10ii",
+ "ua": "Mozilla/5.0 (Linux; Android 10; XQ-AU52) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-AU52",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia 10 III Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 12; XQ-BT44 Build/62.1.A.1.227; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Mobile Safari/537.36 Sleipnir/3.6.0",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-BT44",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia 10 VI",
+ "ua": "Mozilla/5.0 (Linux; Android 14; XQ-ES72 Build/70.0.A.2.233; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.71 Mobile Safari/537.36 Line/14.16.0/IAB",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-ES72",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; XQ-AQ52) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.185 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-AQ52",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Sony XQ-AQ52) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-AQ52",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony Xperia Pro I",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; XQ-BE72) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.148 YaBrowser/22.7.3.82.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "XQ-BE72",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Sony SGP521 (Xperia Z2 Tablet)",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4; SGP521 Build/17.1.A.0.432) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.99 Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "Xperia Tablet",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Sony Xperia Z2 Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; SGP561) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.99 Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "Xperia Tablet",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Sony Tablet S",
+ "ua": "Mozilla/5.0 (Linux; U; Android 3.1; Sony Tablet S Build/THMAS10000) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",
+ "expect": {
+ "vendor": "Sony",
+ "model": "Xperia Tablet",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Sony Tablet Z LTE",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.1; SonySGP321 Build/10.2.C.0.143) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
+ "expect": {
+ "vendor": "Sony",
+ "model": "Xperia Tablet",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Sony BRAVIA 4K GB ATV3",
+ "ua": "Mozilla/5.0 (Linux; Andr0id 9; BRAVIA 4K GB ATV3 Build/PTT1.190515.001.S38) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 OPR/46.0.2207.0 OMI/4.13.0.180.DIA5.104 Model/Sony-BRAVIA-4K-GB-ATV3",
+ "expect": {
+ "vendor": "Sony",
+ "model": "BRAVIA 4K GB ATV3",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Sony BRAVIA 4K GB ATV3",
+ "ua": "Mozilla/5.0 (Linux; Android 9; BRAVIA 4K GB ATV3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "BRAVIA 4K GB ATV3",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Sony Bravia 4k UR2",
+ "ua": "Mozilla/5.0 (Linux: Andr0id 9: BRAVIA 4K UR2 Build/PTT1.190515.001.S104) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 OPR/46.0.2207.0 OMI/4.13.5.431.DIA5HBBTV.250 Model/Sony-BRAVIA-4K-UR2",
+ "expect": {
+ "vendor": "Sony",
+ "model": "BRAVIA 4K UR2",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "PlayStation 4",
+ "ua": "Mozilla/5.0 (PlayStation 4 3.00) AppleWebKit/537.73 (KHTML, like Gecko)",
+ "expect": {
+ "vendor": "Sony",
+ "model": "PlayStation 4",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "PlayStation 5",
+ "ua": "Mozilla/5.0 (Playstation; Playstation 5/1.05) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15",
+ "expect": {
+ "vendor": "Sony",
+ "model": "Playstation 5",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "PlayStation Vita",
+ "ua": "Mozilla/5.0 (PlayStation Vita 3.52) AppleWebKit/537.73 (KHTML, like Gecko) Silk/3.2",
+ "expect": {
+ "vendor": "Sony",
+ "model": "PlayStation Vita",
+ "type": "console"
+ }
+ },
+ {
+ "desc": "Sony SmartWatch 3",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.2; SmartWatch 3 Build/LWX49K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/19.77.34.5 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Sony",
+ "model": "SmartWatch 3",
+ "type": "wearable"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/tcl.json b/test/data/ua/device/tcl.json
new file mode 100644
index 000000000..8c805371d
--- /dev/null
+++ b/test/data/ua/device/tcl.json
@@ -0,0 +1,488 @@
+[
+ {
+ "desc": "TCL 10 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T790Y) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36 EdgA/114.0.1823.43",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T790Y",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 10 5G UW",
+ "ua": "Mozilla/5.0 (Linux; Android 10; T790S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T790S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 10 Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T782H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Mobile Safari/537.36 OPR/64.3.3282.60839",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T782H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 10 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; T799B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.87 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T799B",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 10 SE",
+ "ua": "Mozilla/5.0 (Linux; Android 10; T766H_RU) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.85 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T766H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 10 TabMax",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 9296Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9296Q",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL 10 TabMax 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; 9295G_EEA) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9295G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL 10 TabMax WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 10; 9296G_TR) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9296G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL 10L",
+ "ua": "Mozilla/5.0 (Linux; Android 10; T770B Build/QKQ1.200329.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 GSA/11.41.10.23.arm64",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T770B",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 10L",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T770H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T770H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T781) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T781",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20 Pro 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T810S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36 EdgA/113.0.1774.63",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T810S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20 SE",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T671H Build/RKQ1.201112.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/99.0.4844.73 Mobile Safari/537.36 GoogleApp/13.9.7.23.arm64",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T671H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20 XE",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 5087Z) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "5087Z",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20B",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 6159K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6159K",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 205",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 4187D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "4187D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20E",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 6125A) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/18.0 Chrome/99.0.4844.88 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6125A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20L",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T774H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.59 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T774H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20L Plus",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T775H Build/RKQ1.210107.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.61 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T775H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20R 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T767H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.97 Mobile Safari/537.36 OPR/71.3.3718.67322",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T767H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20S",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T773O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T773O",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 20Y",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 6156D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.134 Mobile Safari/537.36 OPR/70.3.3653.66287",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6156D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30 V 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; T781S Build/RKQ1.210614.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166 Mobile Safari/537.36[FBAN/EMA;FBLC/en_US;FBAV/369.0.0.5.110;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T781S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30 XE 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T767W Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/416.0.0.35.85;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T767W",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 305",
+ "ua": "Mozilla/5.0 (Linux; arm; Android 11; 6102D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.167 YaBrowser/22.7.6.96.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6102D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 306",
+ "ua": "Mozilla/5.0 (Linux; Android 12; 6102H Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/107.0.5304.141 Mobile Safari/537.36[FBAN/EMA;FBLC/it_IT;FBAV/332.0.0.22.108;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6102H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T676H Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T676H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30+",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T676J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T676J",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T776H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.104 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T776H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30 LE",
+ "ua": "Mozilla/5.0 (Linux; Android 12; 4188V Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/112.0.5615.136 Mobile Safari/537.36[FBAN/EMA;FBLC/en_US;FBAV/352.0.0.14.108;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "4188V",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30 SE",
+ "ua": "Mozilla/5.0 (Linux; Android 12; 6165H Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/396.1.0.28.104;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6165H",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 30E",
+ "ua": "Mozilla/5.0 (Linux; Android 12; 6127I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "6127I",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 40 NxtPaper",
+ "ua": "Mozilla/5.0 (Linux; Android 13; T612B Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.53 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T612B",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 4056S",
+ "ua": "Mozilla/5.0 (Mobile; TCL 4056S; rv:84.0) Gecko/84.0 Firefox/84.0 KAIOS/3.0",
+ "expect": {
+ "vendor": "TCL",
+ "model": "4056S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL A3",
+ "ua": "Mozilla/5.0 (Linux; Android 11; A509DL Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Mobile Safari/537.36 GSA/13.18.7.23.arm64",
+ "expect": {
+ "vendor": "TCL",
+ "model": "A509DL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL A30",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 5102L Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/112.0.5615.136 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/413.0.0.30.104;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "5102L",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 40 SE",
+ "ua": "Mozilla/5.0 (Linux; Android 13; T610K Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T610K",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 40 XE 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 13; T609DL Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/125.0.6422.136 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/466.1.0.57.85;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T609DL",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 403",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T431D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T431D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 405",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T506D Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/418.0.0.33.69;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T506D",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 408",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; T507U Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/105.0.5195.136 Mobile Safari/537.36 OPR/75.0.2254.68857",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T507U",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL 40R 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T771K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36 EdgA/114.0.1823.37",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T771K",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL Ion X",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T430W Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.60 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T430W",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL NxtPaper 11",
+ "ua": "Mozilla/5.0 (Linux; Android 13; 9466X Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.179 Safari/537.36 [FB_IAB/FB4A;FBAV/473.0.0.41.81;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9466X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Stylus 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; T779W Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.2 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "T779W",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "TCL Tab 8 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 10; 9048S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9048S",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 8 LE",
+ "ua": "Mozilla/5.0 (Linux; Android 12; 9137W Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.61 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9137W",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 10 FHD 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 9060G Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.5735.196 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9060G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 10 HD 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 9060X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9060X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 10 LTE",
+ "ua": "Mozilla/5.0 (Linux; Android 13; 8196G Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.162 Safari/537.36 [FB_IAB/FB4A;FBAV/471.0.0.35.80;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "8196G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 10 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 13; 8496G Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.61 Safari/537.36 [FB_IAB/FB4A;FBAV/474.0.0.52.74;]",
+ "expect": {
+ "vendor": "TCL",
+ "model": "8496G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 10L",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 8491X_EEA Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/107.0.5304.105 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "8491X",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Tab 10s 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 9080G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "9080G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "TCL Xess P17AA",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; TCL Xess P17AA Build/LMY47D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.85 Safari/537.36",
+ "expect": {
+ "vendor": "TCL",
+ "model": "Xess P17AA",
+ "type": "tablet"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/technisat.json b/test/data/ua/device/technisat.json
new file mode 100644
index 000000000..e9dca81b1
--- /dev/null
+++ b/test/data/ua/device/technisat.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc": "TechniSAT Digit ISIO S SAT receiver",
+ "ua": "Opera/9.80 (Linux sh4; U; HbbTV/1.1.1 (;;;;;); CE-HTML; TechniSat Digit ISIO S; de) Presto/2.9.167 Version/11.50",
+ "expect": {
+ "vendor": "TechniSat",
+ "model": "Digit ISIO S",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "TechniSAT MultyVision SmartTV",
+ "ua": "Opera/9.80 (Linux i686; U; HbbTV/1.1.1 (;;;;;); CE-HTML; TechniSat MultyVision ISIO; de) Presto/2.9.167 Version/11.50",
+ "expect": {
+ "vendor": "TechniSat",
+ "model": "MultyVision ISIO",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/tecno.json b/test/data/ua/device/tecno.json
new file mode 100644
index 000000000..4413f9531
--- /dev/null
+++ b/test/data/ua/device/tecno.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc": "Tecno DroiPad 8D",
+ "ua": "Mozilla/5.0 (Linux; U; Android 5.1; TECNO DP8D Build/LMY47D; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Safari/537.36 OPR/42.0.2254.139280",
+ "expect": {
+ "vendor": "TECNO",
+ "model": "DP8D",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Tecno DroiPad 10A Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; TECNO DP10A Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.74 Safari/537.36",
+ "expect": {
+ "vendor": "TECNO",
+ "model": "DP10A Pro",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Tecno DroiPad 10D 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; TECNO P904) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36 OPR/77.0.4054.90",
+ "expect": {
+ "vendor": "TECNO",
+ "model": "P904",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Tecno KC8",
+ "ua": "Mozilla/5.0 (Linux; Android 10; TECNO KC8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TECNO",
+ "model": "KC8",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Tecno Spark 8C",
+ "ua": "Mozilla/5.0 (Linux; Android 11; TECNO KG5n) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "TECNO",
+ "model": "KG5n",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/tesla.json b/test/data/ua/device/tesla.json
new file mode 100644
index 000000000..34faa3552
--- /dev/null
+++ b/test/data/ua/device/tesla.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc": "Tesla",
+ "ua": "Mozilla/5.0 (X11; GNU/Linux) AppleWebKit/601.1 (KHTML, like Gecko) Tesla QtCarBrowser Safari/601.1",
+ "expect": {
+ "vendor": "Tesla",
+ "model": "undefined",
+ "type": "embedded"
+ }
+ },
+ {
+ "desc": "Tesla",
+ "ua": "Mozilla/5.0 (X11; GNU/Linux) AppleWebKit/537.36 (KHTML, like Gecko) Chromium/79.0.3945.130 Chrome/79.0.3945.130 Safari/537.36 Tesla/2020.16.2.1-e99c70fff409",
+ "expect": {
+ "vendor": "Tesla",
+ "model": "undefined",
+ "type": "embedded"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/ulefone.json b/test/data/ua/device/ulefone.json
new file mode 100644
index 000000000..c7aff3261
--- /dev/null
+++ b/test/data/ua/device/ulefone.json
@@ -0,0 +1,92 @@
+[
+ {
+ "desc": "Ulefone Armor",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; Armor Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.107 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Armor",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 6.0; Armor) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 YaBrowser/20.4.2.101.00 SA/1 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Armor 8 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Armor 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.192 Mobile Safari/537.36 OPR/74.1.3922.71199",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor 8 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Armor 12 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 11; Armor 12 5G Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor 12 5G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Armor 20WT",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Armor 20WT) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/22.0 Chrome/111.0.5563.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor 20WT",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Armor Pad",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Armor Pad Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/431.0.0.30.108;]",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor Pad",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Armor X5 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Armor X5 Pro Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/430.0.0.23.113;]",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Armor X5 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Power Armor 14 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Power Armor14 Pro Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Power Armor14 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Power Armor 18T",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Power Armor 18T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Power Armor 18T",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Ulefone Power Armor 19T",
+ "ua": "Mozilla/5.0 (Linux; Android 12; Power Armor 19T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.192 Mobile Safari/537.36 OPR/74.3.3922.71982",
+ "expect": {
+ "vendor": "Ulefone",
+ "model": "Power Armor 19T",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/vivo.json b/test/data/ua/device/vivo.json
new file mode 100644
index 000000000..cad62f9d5
--- /dev/null
+++ b/test/data/ua/device/vivo.json
@@ -0,0 +1,74 @@
+[
+ {
+ "desc": "Vivo S1 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; vivo 1920) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "1920",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo Y52s",
+ "ua": "Mozilla/5.0 (Linux; Android 10; V2057A Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.89 Mobile Safari/537.36 T7/12.10 SP-engine/2.28.0 baiduboxapp/12.10.0.10 (Baidu; P1 10) NABar/1.0",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "V2057A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo X60",
+ "ua": "Mozilla/5.0 (Linux; Android 11; V2046A; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/8.8.71.0",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "V2046A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo Y79A",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.2; vivo Y79A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/9.0.14.0",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "Y79A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo Y93",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; vivo 1814) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "1814",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo Y97",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1.0; V1813T Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/9.0.14.0",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "V1813T",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo iQOO Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; V1916A; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 VivoBrowser/9.1.10.6",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "V1916A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Vivo 1906 (Y11)",
+ "ua": "Mozilla/5.0 (Linux; Android 11; vivo 1906) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Vivo",
+ "model": "1906",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/vizio.json b/test/data/ua/device/vizio.json
new file mode 100644
index 000000000..d36ae4d30
--- /dev/null
+++ b/test/data/ua/device/vizio.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc": "VIZIO",
+ "ua": "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36 CrKey/1.0.999999 VIZIO SmartCast(Conjure/SX7B-4.6.419.12 FW/7.0.23.2-4 Model/M557-G0)",
+ "expect": {
+ "vendor": "VIZIO",
+ "model": "M557-G0",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "VIZIO",
+ "ua": "VIZIO V506-J09 ViziOS/1.4.512.847.1 WatchFree/24.06.13.2 FancyPlayer/1.1.30-qa ",
+ "expect": {
+ "vendor": "VIZIO",
+ "model": "V506-J09",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/xiaomi.json b/test/data/ua/device/xiaomi.json
new file mode 100644
index 000000000..86593efc0
--- /dev/null
+++ b/test/data/ua/device/xiaomi.json
@@ -0,0 +1,569 @@
+[
+ {
+ "desc": "MIUI Xiaomi Mi MIX 3 5G",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 9; Mi MIX 3 5G MIUI/V10.3.2.0.PEMEUVF)",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi MIX 3 5G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "MIUI POCOPHONE F1",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 9; POCOPHONE F1 MIUI/9.6.27)",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "POCOPHONE F1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "MIUI Xiaomi M2006C3MT",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 10; M2006C3MT MIUI/V12.0.7.0.QCRMIXM)",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2006C3MT",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi 2201117TG",
+ "ua": "Mozilla/5.0 (Linux; Android 11; 2201117TG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.98 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "2201117TG",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi M2004J19C",
+ "ua": "Mozilla/5.0 (Linux; Android 11; M2004J19C Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.77 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2004J19C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi M2006C3MNG",
+ "ua": "Mozilla/5.0 (Linux; Android 11; M2006C3MNG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.85 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2006C3MNG",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi 21061119DG",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; 21061119DG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 YaBrowser/23.3.7.24.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "21061119DG",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi 2013023",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.2; en-US; 2013023 Build/HM2013023) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/10.0.1.512 U3/0.8.0 Mobile Safari/533.1",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "2013023",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "HM NOTE 1W",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi 3C",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.3; zh-CN; MI 3C Build/JLS36C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/9.7.9.439 U3/0.8.0 Mobile Safari/533.1",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI 3C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi 5",
+ "ua": "Mozilla/5.0 (Linux; Android 7.0; MI 5 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI 5",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi 6",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1; MI 6 Build/NMF26X; xx-xx) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI 6",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi 10 Pro",
+ "ua": "Linux; U; Android 13; Mi 10 Pro Build/TKQ1.221114.001",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi 10 Pro",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI 5s Plus",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi A1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; Mi A1 Build/OPR1.170623.026) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi A1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Note",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.4; MI NOTE LTE Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI NOTE LTE",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi One Plus",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; MI-ONE Plus Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI-ONE Plus",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Max 3",
+ "ua": "Mozilla/5.0 (Linux; Android 9; MI MAX 3 Build/PKQ1.181007.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI MAX 3",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi A1",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Mi A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi A1",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi A2 Lite",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Mi A2 Lite) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.62 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi A2 Lite",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi 9 SE",
+ "ua": "Mozilla/5.0 (Linux; Android 9; Mi 9 SE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi 9 SE",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi A2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi CC9",
+ "ua": "Mozilla/5.0 (Linux; U; Android 11; zh-cn; MI CC 9 Build/RKQ1.200826.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.116 Mobile Safari/537.36 XiaoMi/MiuiBrowser/15.5.18",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI CC 9",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.4.4; en-us; MI PAD Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/9.3.2",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI PAD",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD 2",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; MI PAD 2 Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Safari/537.36 [FB_IAB/FB4A;FBAV/137.0.0.24.91;]",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI PAD 2",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD 2",
+ "ua": "Mozilla/5.0 (Linux; x86_64; Android 5.1; MI PAD 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 YaBrowser/20.11.2.69.01 Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI PAD 2",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD 3",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 7.0; MI PAD 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.216 YaBrowser/21.5.6.56.01 Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI PAD 3",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD 4",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 8.1.0; MI PAD 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 YaBrowser/19.9.1.126.01 Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI PAD 4",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD 4 PLUS",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; MI PAD 4 PLUS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MI PAD 4 PLUS",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi MI PAD 4 WiFi",
+ "ua": "Mozilla/5.0 (Linux; Android 8.1; Mi Pad4 Wi-Fi) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Mobile Safari/537.36 EdgA/86.0.622.61",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Mi Pad4 Wi-Fi",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Pad 5",
+ "ua": "Mozilla/5.0 (Linux; Android 13; 21051182G Build/TKQ1.221013.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36 Flipboard/4.3.31/5486,4.3.31.5486",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "21051182G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Pad 5 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; M2105K81AC Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Safari/537.36 Line/13.15.1/IAB",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2105K81AC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Pad 5 Pro 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; M2105K81C) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2105K81C",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Pad 6 Max 14",
+ "ua": "Mozilla/5.0 (Linux; U; Android 14; zh-tw; 2307BRPDCC Build/UKQ1.230804.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/109.0.5414.118 Mobile Safari/537.36 Device/yudi Model/2307BRPDCC XiaoMi/MiuiBrowser/14.10.6",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "2307BRPDCC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Pad 6 Pro",
+ "ua": "Mozilla/5.0 (Linux; U; Android 13; en-US; 23046RP50C Build/TKQ1.221114.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.6.2.1316 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "23046RP50C",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "24018RPACC",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO X2",
+ "ua": "Mozilla/5.0 (Linux; Android 10; POCO X2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "POCO X2",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO X3 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 11; M2102J20SI) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2102J20SI",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO X3 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 12; M2102J20SG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2102J20SG",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO X3 NFC",
+ "ua": "Mozilla/5.0 (Linux; Android 12; M2007J20CG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2007J20CG",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO M2 Pro",
+ "ua": "Mozilla/5.0 (Linux; arm_64; Android 11; POCO M2 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 YaBrowser/22.11.7.42.00 SA/3 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "POCO M2 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi POCO M3",
+ "ua": "Mozilla/5.0 (Linux; Android 10; M2010J19CI) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2010J19CI",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi POCOPHONE F1",
+ "ua": "Mozilla/5.0 (Linux; Android 10; POCOPHONE F1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "POCOPHONE F1",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi 4A",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Redmi 10C",
+ "ua": "Mozilla/5.0 (Linux; Android 12; 220333QAG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "220333QAG",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi K30 5G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Redmi K30 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Redmi K30 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi K30 Pro",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Redmi Note 3",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; Redmi Note 3 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi Note 3",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "Xiaomi Redmi Note 9 Pro Max",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Redmi Note 9 Pro Max) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.99 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi Note 9 Pro Max",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Note 9S",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Redmi Note 9S) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi Note 9S",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Note 10 5G",
+ "ua": "Mozilla/5.0 (Linux; Android 12; M2103K19C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2103K19C",
+ "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",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2101K6P",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Note 10 Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 12; M2101K6G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "M2101K6G",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Note 8",
+ "ua": "Mozilla/5.0 (Linux; Android 10; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi Note 8",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Note 12 Turbo",
+ "ua": "Mozilla/5.0 (Linux; Android 13; 23049RAD8C; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/16.7.1.1",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "23049RAD8C",
+ "type": "mobile"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Pad",
+ "ua": "Mozilla/5.0 (Linux; U; Android 12; id-id; Redmi Pad Build/SP1A.210812.016) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/112.0.5615.136 Mobile Safari/537.36 XiaoMi/MiuiBrowser/14.1.1-gn",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "Redmi Pad",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Pad",
+ "ua": "Mozilla/5.0 (Linux; Android 14; 22081283G Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Safari/537.36 Flipboard/4.3.31/5486,4.3.31.5486",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "22081283G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Pad Pro",
+ "ua": "Mozilla/5.0 (Linux; Android 14; 2405CRPFDG Build/UKQ1.240116.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.97 Safari/537.36 [FB_IAB/FB4A;FBAV/476.0.0.49.74;] FBNV/1",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "2405CRPFDG",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Pad SE",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 14; 23073RPBFG Build/UKQ1.231003.002)",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "23073RPBFG",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "XiaoMi Redmi Pad SE 8.7",
+ "ua": "Mozilla/5.0 (Linux; Android 14; 24076RP19G Build/UP1A.231005.007; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Mobile Safari/537.36 Line/14.18.1/IAB",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "24076RP19G",
+ "type": "tablet"
+ }
+ },
+ {
+ "desc": "Xiaomi TV",
+ "ua": "Mozilla/5.0 (Linux; Android 10; MiTV-MOOQ0 Build/QTG3.200305.006; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/94.0.4606.61 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MiTV-MOOQ0",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi TV",
+ "ua": "Mozilla/5.0 (Linux; Android 9; MiTV4I Build/PI; en-in) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36 Puffin/7.8.3.40913AP",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MiTV4I",
+ "type": "smarttv"
+ }
+ },
+ {
+ "desc": "Xiaomi Mi Box",
+ "ua": "Mozilla/5.0 (Linux; Android 9; MIBOX3 Build/PI; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/119.0.6045.193 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Xiaomi",
+ "model": "MIBOX3",
+ "type": "smarttv"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/device/zte.json b/test/data/ua/device/zte.json
new file mode 100644
index 000000000..5b43beae4
--- /dev/null
+++ b/test/data/ua/device/zte.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc": "ZTE Blade A6",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; ZTE BLADE A0620 Build/NMF26F; ru-ru) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Mobile Safari/537.36 Puffin/9.2.0.50586AP",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "BLADE A0620",
+ "type": "mobile"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/engine-test.json b/test/data/ua/engine/engine-all.json
similarity index 82%
rename from test/engine-test.json
rename to test/data/ua/engine/engine-all.json
index 6726d27d9..c68603112 100644
--- a/test/engine-test.json
+++ b/test/data/ua/engine/engine-all.json
@@ -1,4 +1,13 @@
[
+ {
+ "desc" : "ArkWeb",
+ "ua" : "Mozilla/5.0 (Phone; OpenHarmony 4.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile",
+ "expect" :
+ {
+ "name" : "ArkWeb",
+ "version" : "4.1.6.1"
+ }
+ },
{
"desc" : "Blink",
"ua" : "Mozilla/5.0 (Linux; Android 7.0; SM-G920I Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) OculusBrowser/3.4.9 SamsungBrowser/4.0 Chrome/57.0.2987.146 Mobile VR Safari/537.36",
@@ -53,6 +62,24 @@
"version" : "4.5.4"
}
},
+ {
+ "desc" : "LibWeb",
+ "ua" : "Mozilla/5.0 (Linux; x86_64) Ladybird/1.0",
+ "expect" :
+ {
+ "name" : "LibWeb",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "LibWeb",
+ "ua" : "Mozilla/4.0 (SerenityOS; x86) LibWeb+LibJS (Not KHTML, nor Gecko) LibWeb",
+ "expect" :
+ {
+ "name" : "LibWeb",
+ "version" : "undefined"
+ }
+ },
{
"desc" : "NetFront",
"ua" : "Mozilla/4.0 (PDA; Windows CE/1.0.1) NetFront/3.0",
@@ -71,6 +98,15 @@
"version" : "2.8.149"
}
},
+ {
+ "desc" : "Servo",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Servo/1.0 Firefox/111.0",
+ "expect" :
+ {
+ "name" : "Servo",
+ "version" : "1.0"
+ }
+ },
{
"desc" : "Tasman",
"ua" : "Mozilla/4.0 (compatible; MSIE 6.0; PPC Mac OS X 10.4.7; Tasman 1.0)",
diff --git a/test/data/ua/extension/cli.json b/test/data/ua/extension/cli.json
new file mode 100644
index 000000000..ce89135d9
--- /dev/null
+++ b/test/data/ua/extension/cli.json
@@ -0,0 +1,72 @@
+[
+ {
+ "desc" : "curl",
+ "ua" : "curl/7.38.0",
+ "expect" :
+ {
+ "name" : "curl",
+ "version" : "7.38.0",
+ "type" : "cli"
+ }
+ },
+ {
+ "desc" : "ELinks",
+ "ua" : "ELinks/0.11.4-3-lite (textmode; Debian; Linux 2.6.26-1-686 i686;",
+ "expect" :
+ {
+ "name" : "ELinks",
+ "version" : "0.11.4-3-lite",
+ "type" : "cli"
+ }
+ },
+ {
+ "desc" : "ELinks",
+ "ua" : "ELinks (0.11.3; Linux 2.6.23-hardened-r4 i686; 166x55)",
+ "expect" :
+ {
+ "name" : "ELinks",
+ "version" : "0.11.3",
+ "type" : "cli"
+ }
+ },
+ {
+ "desc" : "HTTPie",
+ "ua" : "HTTPie/0.9.9",
+ "expect" :
+ {
+ "name" : "HTTPie",
+ "version" : "0.9.9",
+ "type" : "cli"
+ }
+ },
+ {
+ "desc" : "lynx",
+ "ua" : "Lynx 2.8.8dev.3",
+ "expect" :
+ {
+ "name" : "Lynx",
+ "version" : "2.8.8dev.3",
+ "type" : "cli"
+ }
+ },
+ {
+ "desc" : "lynx",
+ "ua" : "Lynx/2.6",
+ "expect" :
+ {
+ "name" : "Lynx",
+ "version" : "2.6",
+ "type" : "cli"
+ }
+ },
+ {
+ "desc" : "wget",
+ "ua" : "Wget/1.21.1",
+ "expect" :
+ {
+ "name" : "Wget",
+ "version" : "1.21.1",
+ "type" : "cli"
+ }
+ }
+]
diff --git a/test/data/ua/extension/crawler.json b/test/data/ua/extension/crawler.json
new file mode 100644
index 000000000..f754b6650
--- /dev/null
+++ b/test/data/ua/extension/crawler.json
@@ -0,0 +1,1063 @@
+[
+ {
+ "desc" : "360Spider",
+ "ua" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0); 360Spider",
+ "expect" :
+ {
+ "name" : "360Spider",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "AdIdxBot",
+ "ua" : "Mozilla/5.0 (compatible; adidxbot/2.0; +http://www.bing.com/bingbot.htm)",
+ "expect" :
+ {
+ "name" : "adidxbot",
+ "version" : "2.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "AdsBot Mobile Web",
+ "ua" : "AdsBot-Google (+http://www.google.com/adsbot.html)",
+ "expect" :
+ {
+ "name" : "AdsBot-Google",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "AdsBot Mobile Web",
+ "ua" : "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; AdsBot-Google-Mobile; +http://www.google.com/mobile/adsbot.html)",
+ "expect" :
+ {
+ "name" : "AdsBot-Google-Mobile",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "AdSense",
+ "ua" : "Mediapartners-Google",
+ "expect" :
+ {
+ "name" : "Mediapartners-Google",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "AhrefsBot",
+ "ua" : "Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)",
+ "expect" :
+ {
+ "name" : "AhrefsBot",
+ "version" : "7.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "AI2Bot",
+ "ua" : "Mozilla/5.0 (compatible) AI2Bot (+https://www.allenai.org/crawler)",
+ "expect" :
+ {
+ "name" : "AI2Bot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "aiHitBot",
+ "ua" : "Mozilla/5.0 (compatible; aiHitBot/2.9; +https://www.aihitdata.com/about)",
+ "expect" :
+ {
+ "name" : "aiHitBot",
+ "version" : "2.9",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Applebot",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B410 Safari/600.1.4 (Applebot/0.1;+http://www.apple.com/go/applebot)",
+ "expect" :
+ {
+ "name" : "Applebot",
+ "version" : "0.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Applebot-Extended",
+ "ua" : "Applebot-Extended",
+ "expect" :
+ {
+ "name" : "Applebot-Extended",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Amazonbot",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like Gecko) Version/8.0.2 Safari/600.2.5 (Amazonbot/0.1; +https://developer.amazon.com/support/amazonbot)",
+ "expect" :
+ {
+ "name" : "Amazonbot",
+ "version" : "0.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Anthropic AI",
+ "ua" : "anthropic-ai",
+ "expect" :
+ {
+ "name" : "anthropic-ai",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Archive.org Bot",
+ "ua" : "ia_archiver/8.1 (Windows 2000 1.9; en-US;)",
+ "expect" :
+ {
+ "name" : "ia_archiver",
+ "version" : "8.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Archive.org Bot",
+ "ua" : "Mozilla/5.0 (compatible; archive.org_bot/3.3.0 +https://archive.org/details/archive.org_bot)",
+ "expect" :
+ {
+ "name" : "archive.org_bot",
+ "version" : "3.3.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider",
+ "ua" : "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)",
+ "expect" :
+ {
+ "name" : "Baiduspider",
+ "version" : "2.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-ads",
+ "ua" : "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0; Baiduspider-ads) Gecko/17.0 Firefox/17.0",
+ "expect" :
+ {
+ "name" : "Baiduspider-ads",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-cpro",
+ "ua" : "Mozilla/5.0 (compatible; Baiduspider-cpro; +http://www.baidu.com/search/spider.html)",
+ "expect" :
+ {
+ "name" : "Baiduspider-cpro",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-favo",
+ "ua" : "Baiduspider-favo",
+ "expect" :
+ {
+ "name" : "Baiduspider-favo",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-image",
+ "ua" : "Baiduspider-image+(+http://www.baidu.com/search/spider.htm)",
+ "expect" :
+ {
+ "name" : "Baiduspider-image",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-news",
+ "ua" : "Baiduspider-news",
+ "expect" :
+ {
+ "name" : "Baiduspider-news",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-render",
+ "ua" : "Mozilla/5.0 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)",
+ "expect" :
+ {
+ "name" : "Baiduspider-render",
+ "version" : "2.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Baiduspider-video",
+ "ua" : "Baiduspider-video",
+ "expect" :
+ {
+ "name" : "Baiduspider-video",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Bingbot",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/",
+ "expect" :
+ {
+ "name" : "bingbot",
+ "version" : "2.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Bravebot",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Bravebot/1.0; +https://search.brave.com/help/brave-search-crawler) Chrome/W.X.Y.Z Safari/537.36",
+ "expect" :
+ {
+ "name" : "Bravebot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Bytespider",
+ "ua" : "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.1511.1269 Mobile Safari/537.36; Bytespider",
+ "expect" :
+ {
+ "name" : "Bytespider",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "ClaudeBot",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)",
+ "expect" :
+ {
+ "name" : "ClaudeBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "CCBot",
+ "ua" : "CCBot/1.0 (+https://commoncrawl.org/bot.html)",
+ "expect" :
+ {
+ "name" : "CCBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "ChatGLM-Spider",
+ "ua" : "Mozilla/5.0 (compatible; ChatGLM-Spider/1.0; +https://chatglm.cn/)",
+ "expect" :
+ {
+ "name" : "ChatGLM-Spider",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "ClaudeWeb",
+ "ua" : "Claude-Web/1.0 (web crawler; +https://www.anthropic.com/; bots@anthropic.com)",
+ "expect" :
+ {
+ "name" : "Claude-Web",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Coc Coc Bot (web)",
+ "ua" : "Mozilla/5.0 (compatible; coccocbot-web/1.0; +http://help.coccoc.com/searchengine)",
+ "expect" :
+ {
+ "name" : "coccocbot-web",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Coc Coc Bot (image)",
+ "ua" : "Mozilla/5.0 (compatible; coccocbot-image/1.0; +http://help.coccoc.com/searchengine)",
+ "expect" :
+ {
+ "name" : "coccocbot-image",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "cohere-training-data-crawler",
+ "ua" : "cohere-training-data-crawler (+crawler@cohere.ai)",
+ "expect" :
+ {
+ "name" : "cohere-training-data-crawler",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Cotoyogi",
+ "ua" : "Mozilla/5.0 (compatible; Cotoyogi/4.0; +https://ds.rois.ac.jp/center8/crawler/)",
+ "expect" :
+ {
+ "name" : "Cotoyogi",
+ "version" : "4.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Coveobot",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) (compatible; Coveobot/2.0;+http://www.coveo.com/bot.html)",
+ "expect" :
+ {
+ "name" : "Coveobot",
+ "version" : "2.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "CriteoBot",
+ "ua" : "CriteoBot/0.1 (+https://www.criteo.com/criteo-crawler/)",
+ "expect" :
+ {
+ "name" : "CriteoBot",
+ "version" : "0.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "DataForSEO",
+ "ua" : "Mozilla/5.0 (compatible; DataForSeoBot; +https://dataforseo.com/dataforseo-bot)",
+ "expect" :
+ {
+ "name" : "DataForSeoBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Daum",
+ "ua" : "Mozilla/5.0 (compatible; MSIE or Firefox mutant;) Daum 4.1",
+ "expect" :
+ {
+ "name" : "Daum",
+ "version" : "4.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Daumoa",
+ "ua" : "Mozilla/5.0 (compatible; MSIE or Firefox mutant; not on Windows server;) Daumoa 4.0",
+ "expect" :
+ {
+ "name" : "Daumoa",
+ "version" : "4.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Daumoa-image",
+ "ua" : "Mozilla/5.0 (compatible; MSIE or Firefox mutant; not on Windows server;) Daumoa-image/1.0",
+ "expect" :
+ {
+ "name" : "Daumoa-image",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "DeepSeekBot",
+ "ua" : "DeepSeekBot",
+ "expect" :
+ {
+ "name" : "DeepSeekBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Diffbot",
+ "ua" : "Diffbot/0.1",
+ "expect" :
+ {
+ "name" : "Diffbot",
+ "version" : "0.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Dotbot",
+ "ua" : "Mozilla/5.0 (compatible; DotBot/1.2; +https://opensiteexplorer.org/dotbot; help@moz.com)",
+ "expect" :
+ {
+ "name" : "DotBot",
+ "version" : "1.2",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "DuckDuckBot",
+ "ua" : "DuckDuckBot/1.1; ( http://duckduckgo.com/duckduckbot.html)",
+ "expect" :
+ {
+ "name" : "DuckDuckBot",
+ "version" : "1.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "DuckDuckGo-Favicons-Bot",
+ "ua" : "DuckDuckGo-Favicons-Bot/1.0",
+ "expect" :
+ {
+ "name" : "DuckDuckGo-Favicons-Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Elastic",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/124.0.6367.29 Safari/537.36 Elastic/Synthetics",
+ "expect" :
+ {
+ "name" : "Elastic",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Exabot",
+ "ua" : "Mozilla/5.0 (compatible; Exabot/3.0; +http://www.exabot.com/go/robot)",
+ "expect" :
+ {
+ "name" : "Exabot",
+ "version" : "3.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "FacebookBot",
+ "ua" : "Mozilla/5.0 (compatible; FacebookBot/1.0; +https://developers.facebook.com/docs/sharing/webmasters/facebookbot/",
+ "expect" :
+ {
+ "name" : "FacebookBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "FacebookExternalHit",
+ "ua" : "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)",
+ "expect" :
+ {
+ "name" : "facebookexternalhit",
+ "version" : "1.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "FacebookExternalHit",
+ "ua" : "facebookcatalog/1.0",
+ "expect" :
+ {
+ "name" : "facebookcatalog",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "FirecrawlAgent",
+ "ua" : "Mozilla/5.0 (compatible; FirecrawlAgent/1.0)",
+ "expect" :
+ {
+ "name" : "FirecrawlAgent",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Googlebot-Video",
+ "ua" : "Googlebot-Video/1.0",
+ "expect" :
+ {
+ "name" : "Googlebot-Video",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Googlebot",
+ "ua" : "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
+ "expect" :
+ {
+ "name" : "Googlebot",
+ "version" : "2.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Googlebot Image",
+ "ua" : "Googlebot-Image/1.0",
+ "expect" :
+ {
+ "name" : "Googlebot-Image",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Googlebot Video",
+ "ua" : "Googlebot-Video/1.0",
+ "expect" :
+ {
+ "name" : "Googlebot-Video",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Googlebot News",
+ "ua" : "Googlebot-News/1.0",
+ "expect" :
+ {
+ "name" : "Googlebot-News",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Google Storebot",
+ "ua" : "Storebot-Google/1.0",
+ "expect" :
+ {
+ "name" : "Storebot-Google",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Google InspectionTool",
+ "ua" : "Mozilla/5.0 (compatible; Google-InspectionTool/1.0;)",
+ "expect" :
+ {
+ "name" : "Google-InspectionTool",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "GoogleOther",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GoogleOther) Chrome/41.0.2272.96 Safari/537.36",
+ "expect" :
+ {
+ "name" : "GoogleOther",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "GoogleOther-Image",
+ "ua" : "GoogleOther-Image/1.0",
+ "expect" :
+ {
+ "name" : "GoogleOther-Image",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "GoogleOther-Video",
+ "ua" : "GoogleOther-Video/1.0",
+ "expect" :
+ {
+ "name" : "GoogleOther-Video",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Google-Safety",
+ "ua" : "Google-Safety",
+ "expect" :
+ {
+ "name" : "Google-Safety",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "GPTBot",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0; +https://openai.com/gptbot)",
+ "expect" :
+ {
+ "name" : "GPTBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "HuggingFace-Bot",
+ "ua" : "Mozilla/5.0 (compatible; HuggingFace-Bot/1.0; +https://huggingface.co/)",
+ "expect" :
+ {
+ "name" : "HuggingFace-Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "iAskBot",
+ "ua" : "Mozilla/5.0 AppleWebKit/605.1.15 (KHTML, like Gecko; compatible; iAskBot/1.0; +https://iask.ai/) Chrome/120.0.6099.119 Safari/605.1.15",
+ "expect" :
+ {
+ "name" : "iAskBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "ImagesiftBot",
+ "ua" : "Mozilla/5.0 (compatible; ImagesiftBot; +imagesift.com)",
+ "expect" :
+ {
+ "name" : "ImagesiftBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Kangaroo Bot",
+ "ua" : "Mozilla/5.0 (compatible; Kangaroo Bot/1.0)",
+ "expect" :
+ {
+ "name" : "Kangaroo Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Linespider",
+ "ua" : "Mozilla/5.0 (compatible; Linespider/1.1; +https://lin.ee/4dwXkTH)",
+ "expect" :
+ {
+ "name" : "Linespider",
+ "version" : "1.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "LinkedInBot",
+ "ua" : "LinkedInBot/1.0 (compatible; Mozilla/5.0; Apache-HttpClient +http://www.linkedin.com)",
+ "expect" :
+ {
+ "name" : "LinkedInBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "magpie-crawler",
+ "ua" : "magpie-crawler/1.1 (robots-txt-checker; +http://www.brandwatch.net)",
+ "expect" :
+ {
+ "name" : "magpie-crawler",
+ "version" : "1.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Meta-ExternalAgent",
+ "ua" : "meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)",
+ "expect" :
+ {
+ "name" : "meta-externalagent",
+ "version" : "1.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "MJ12bot",
+ "ua" : "Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)",
+ "expect" :
+ {
+ "name" : "MJ12bot",
+ "version" : "v1.4.8",
+ "major" : "1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "MojeekBot",
+ "ua" : "Mozilla/5.0 (compatible; MojeekBot/0.11; +https://www.mojeek.com/bot.html)",
+ "expect" :
+ {
+ "name" : "MojeekBot",
+ "version" : "0.11",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Omgili",
+ "ua" : "omgili/0.5 +https://omgili.com",
+ "expect" :
+ {
+ "name" : "omgili",
+ "version" : "0.5",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Omgilibot",
+ "ua" : "omgilibot/0.3 +http://www.omgili.com/Crawler.html",
+ "expect" :
+ {
+ "name" : "omgilibot",
+ "version" : "0.3",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Onespot",
+ "ua" : "Mozilla/5.0 (compatible; Onespot-ScraperBot/1.0; +https://www.onespot.com/identifying-traffic.html)",
+ "expect" :
+ {
+ "name" : "Onespot-ScraperBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "OpenAI Search",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot",
+ "expect" :
+ {
+ "name" : "OAI-SearchBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "PanguBot",
+ "ua" : "Mozilla/5.0 (compatible; PanguBot/1.0)",
+ "expect" :
+ {
+ "name" : "PanguBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "PerplexityBot",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)",
+ "expect" :
+ {
+ "name" : "PerplexityBot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "PetalBot",
+ "ua" : "Mozilla/5.0 (compatible;PetalBot; +https://webmaster.petalsearch.com/site/petalbot) ",
+ "expect" :
+ {
+ "name" : "PetalBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "PetalBot",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; PetalBot;+https://webmaster.petalsearch.com/site/petalbot)",
+ "expect" :
+ {
+ "name" : "PetalBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Qwantbot",
+ "ua" : "Mozilla/5.0 (compatible; Qwantbot/1.0_12345; +https://help.qwant.com/bot/)",
+ "expect" :
+ {
+ "name" : "Qwantbot",
+ "version" : "1.0_12345",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Qwantbot",
+ "ua" : "Mozilla/5.0 (compatible; Qwantbot-prod51071/1.0; +Qwantbot@qwant.com)",
+ "expect" :
+ {
+ "name" : "Qwantbot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Qwantbot",
+ "ua" : "Mozilla/5.0 (compatible; Qwantbot-news/2.0; +https://help.qwant.com/bot/)",
+ "expect" :
+ {
+ "name" : "Qwantbot",
+ "version" : "2.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Replicate-Bot",
+ "ua" : "Mozilla/5.0 (compatible; Replicate-Bot/1.0; +https://replicate.com/)",
+ "expect" :
+ {
+ "name" : "Replicate-Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "RunPod-Bot",
+ "ua" : "Mozilla/5.0 (compatible; RunPod-Bot/1.0; +https://runpod.io/)",
+ "expect" :
+ {
+ "name" : "RunPod-Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "SemrushBot",
+ "ua" : "Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)",
+ "expect" :
+ {
+ "name" : "SemrushBot",
+ "version" : "7",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "SemrushBot for Backlink Audit tool",
+ "ua" : "Mozilla/5.0 (compatible; SemrushBot-BA; +http://www.semrush.com/bot.html)",
+ "expect" :
+ {
+ "name" : "SemrushBot-BA",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "SemrushBot for On Page SEO Checker tool",
+ "ua" : "Mozilla/5.0 (compatible; SemrushBot-SI/0.97; +http://www.semrush.com/bot.html)",
+ "expect" :
+ {
+ "name" : "SemrushBot-SI",
+ "version" : "0.97",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "SemrushBot for ContentShake AI tool",
+ "ua" : "Mozilla/5.0 (compatible; SemrushBot-OCOB/1; +https://www.semrush.com/bot/)",
+ "expect" :
+ {
+ "name" : "SemrushBot-OCOB",
+ "version" : "1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "SeznamBot",
+ "ua" : "Mozilla/5.0 (compatible; SeznamBot/4.0-RC1; +http://napoveda.seznam.cz/seznambot-intro/)",
+ "expect" :
+ {
+ "name" : "SeznamBot",
+ "version" : "4.0-RC1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Sogou",
+ "ua" : "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)",
+ "expect" :
+ {
+ "name" : "Sogou web spider",
+ "version" : "4.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Startpage",
+ "ua" : "StartpagePrivateImageProxy/3.0 (https://www.startpage.com/; support@startpage.com) aiohttp.client/3.11.11",
+ "expect" :
+ {
+ "name" : "StartpagePrivateImageProxy",
+ "version" : "3.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Teoma",
+ "ua" : "Mozilla/2.0 (compatible; Ask Jeeves/Teoma; +http://sp.ask.com/docs/about/tech_crawling.html)",
+ "expect" :
+ {
+ "name" : "Teoma",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Timpibot",
+ "ua" : "Timpibot/0.8 (+http://www.timpi.io)",
+ "expect" :
+ {
+ "name" : "Timpibot",
+ "version" : "0.8",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Together-Bot",
+ "ua" : "Mozilla/5.0 (compatible; Together-Bot/1.0; +https://together.ai/)",
+ "expect" :
+ {
+ "name" : "Together-Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "TurnitinBot",
+ "ua" : "TurnitinBot (https://turnitin.com/robot/crawlerinfo.html)",
+ "expect" :
+ {
+ "name" : "TurnitinBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "xAI-Bot",
+ "ua" : "Mozilla/5.0 (compatible; xAI-Bot/1.0; +https://x.ai/)",
+ "expect" :
+ {
+ "name" : "xAI-Bot",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "VelenPublicWebCrawler",
+ "ua" : "Mozilla/5.0 (compatible; VelenPublicWebCrawler/1.0; +https://velen.io)",
+ "expect" :
+ {
+ "name" : "VelenPublicWebCrawler",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "v0bot",
+ "ua" : "v0bot",
+ "expect" :
+ {
+ "name" : "v0bot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Yahoo! Japan",
+ "ua" : "Y!J-BRW/1.0 (https://www.yahoo-help.jp/app/answers/detail/p/595/a_id/42716)",
+ "expect" :
+ {
+ "name" : "Y!J-BRW",
+ "version" : "1.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Yahoo! Slurp",
+ "ua" : "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp) ",
+ "expect" :
+ {
+ "name" : "Yahoo! Slurp",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "YandexBot",
+ "ua" : "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)",
+ "expect" :
+ {
+ "name" : "YandexBot",
+ "version" : "3.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "Yeti",
+ "ua" : "Mozilla/5.0 (compatible; Yeti/1.1; +http://naver.me/spd)",
+ "expect" :
+ {
+ "name" : "Yeti",
+ "version" : "1.1",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "YisouSpider",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 YisouSpider/5.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "YisouSpider",
+ "version" : "5.0",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "YisouSpider",
+ "ua" : "YisouSpider",
+ "expect" :
+ {
+ "name" : "YisouSpider",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ },
+ {
+ "desc" : "YouBot",
+ "ua" : "YouBot (+http://www.you.com)",
+ "expect" :
+ {
+ "name" : "YouBot",
+ "version" : "undefined",
+ "type" : "crawler"
+ }
+ }
+]
diff --git a/test/data/ua/extension/email.json b/test/data/ua/extension/email.json
new file mode 100644
index 000000000..8acd5d4e7
--- /dev/null
+++ b/test/data/ua/extension/email.json
@@ -0,0 +1,232 @@
+[
+ {
+ "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" : "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" : "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" : "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" : "Evolution",
+ "ua" : "Evolution/3.52.3",
+ "expect" :
+ {
+ "name" : "Evolution",
+ "version" : "3.52.3",
+ "type" : "email"
+ }
+ },
+ {
+ "desc" : "eM Client",
+ "ua" : "eMClient/9.2.2157.0",
+ "expect" :
+ {
+ "name" : "eMClient",
+ "version" : "9.2.2157.0",
+ "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" : "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" : "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" : "NaverMailApp",
+ "ua" : "NaverMailApp/2.1.23 (Android 10; SM-N960N)",
+ "expect" :
+ {
+ "name" : "NaverMailApp",
+ "version" : "2.1.23",
+ "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" : "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" : "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" : "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" : "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" : "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" : "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" : "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"
+ }
+ }
+]
diff --git a/test/data/ua/extension/extra-devices.json b/test/data/ua/extension/extra-devices.json
new file mode 100644
index 000000000..174c4adbe
--- /dev/null
+++ b/test/data/ua/extension/extra-devices.json
@@ -0,0 +1,342 @@
+[{
+ "desc": "Essential PH-1",
+ "ua": "Mozilla/5.0 (Linux; Android 9; PH-1 Build/PPR1.180905.036) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.86 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Essential",
+ "model": "PH-1",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "Gigaset Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Gigaset QV830 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Gigaset",
+ "model": "QV830",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "RCA Voyager III Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; RCT6973W43 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "RCA",
+ "model": "RCT6973W43",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "RCA Voyager II Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0; RCT6773W22B Build/LRX21M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "RCA",
+ "model": "RCT6773W22B",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Verizon Quanta Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; QMV7B Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Verizon",
+ "model": "QMV7B",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Verizon Ellipsis 8 Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; QTAQZ3 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Verizon",
+ "model": "QTAQZ3",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Verizon Ellipsis 8HD Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; QTASUN1 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Safari/537.36",
+ "expect": {
+ "vendor": "Verizon",
+ "model": "QTASUN1",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Dell Venue 8 Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Venue 8 3830 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Dell",
+ "model": "Venue 8 3830",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Dell Venue 7 Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Venue 7 3730 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Dell",
+ "model": "Venue 7 3730",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Barnes & Noble Nook HD+ Tablet",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.1.2; en-us; Barnes & Noble Nook HD+ Build/JZO54K; CyanogenMod-10) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Barnes & Noble",
+ "model": "Nook HD+",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Barnes & Noble V400 Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 4.0.4; BNTV400 Build/IMM76L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.111 Safari/537.36",
+ "expect": {
+ "vendor": "Barnes & Noble",
+ "model": "V400",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "NuVision TM101A540N Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1; TM101A540N Build/LMY47I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/50.0.2661.86 Safari/537.36",
+ "expect": {
+ "vendor": "NuVision",
+ "model": "TM101A540N",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "ZTE-Z431",
+ "ua": "ZTE-Z431/1.4.0 NetFront/4.2 QTV5.1 Profile/MIDP-2.1 Configuration/CLDC-1.1",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "Z431",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "ZTE",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.1.2; en-us; ZTE-Z740G Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "Z740G",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "ZTE K Series Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0.1; K88 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "K88",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "ZTE Nubia Red Magic 3",
+ "ua": "Mozilla/5.0 (Linux; Android 9; NX629J Build/PKQ1.190321.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/45016 Mobile Safari/537.36 MMWEBID/4064 MicroMessenger/7.0.10.1580(0x27000A34) Process/tools NetType/WIFI Language/zh_CN ABI/arm64",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "NX629J",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "ZTE Blade A5",
+ "ua": "Mozilla/5.0 (Linux; Android 9; ZTE Blade A5 2019) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "Blade A5 2019",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "ZTE BLADE V0730",
+ "ua": "Mozilla/5.0 (Linux; Android 6.0; ZTE BLADE V0730) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.116 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "BLADE V0730",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "ZTE B2017G",
+ "ua": "Mozilla/5.0 (Linux; Android 7.1.1; ZTE B2017G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.93 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "ZTE",
+ "model": "B2017G",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "Swizz GEN610",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; GEN610 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Swiss",
+ "model": "GEN610",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "Swizz ZUR700",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; ZUR700 Build/KVT49L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Safari/537.36",
+ "expect": {
+ "vendor": "Swiss",
+ "model": "ZUR700",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Zeki TB782b Tablet",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; en-US; TB782B Build/IMM76D) AppleWebKit/534.31 (KHTML, like Gecko) UCBrowser/9.0.2.299 U3/0.8.0 Mobile Safari/534.31",
+ "expect": {
+ "vendor": "Zeki",
+ "model": "TB782B",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Dragon Touch Tablet",
+ "ua": "Mozilla/5.0 (Linux; Android 4.0.4; DT9138B Build/IMM76D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.72 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Dragon Touch",
+ "model": "9138B",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Insignia Tablet",
+ "ua": "Mozilla/5.0 (Linux; U; Android 6.0.1; NS-P08A7100 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Insignia",
+ "model": "NS-P08A7100",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Voice Xtreme V75",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.2.1; en-us; V75 Build/JOP40D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
+ "expect": {
+ "vendor": "Voice",
+ "model": "V75",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "LvTel V11",
+ "ua": "Mozilla/5.0 (Linux; Android 5.1.1; V11 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "LvTel",
+ "model": "V11",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "Envizen Tablet V100MD",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; V100MD Build/V100MD.20130816) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
+ "expect": {
+ "vendor": "Envizen",
+ "model": "V100MD",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Rotor Tablet",
+ "ua": "mozilla/5.0 (linux; android 5.0.1; tu_1491 build/lrx22c) applewebkit/537.36 (khtml, like gecko) chrome/43.0.2357.93 safari/537.36",
+ "expect": {
+ "vendor": "Rotor",
+ "model": "1491",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "MachSpeed Tablets",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; Trio 7.85 vQ Build/Trio_7.85_vQ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36",
+ "expect": {
+ "vendor": "MachSpeed",
+ "model": "Trio 7.85 vQ",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Trinity Tablets",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.1; Trinity T101 Build/LRX22C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Safari/537.36",
+ "expect": {
+ "vendor": "Trinity",
+ "model": "T101",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "NextBook Next7",
+ "ua": "Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Next7P12 Build/IMM76I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30",
+ "expect": {
+ "vendor": "NextBook",
+ "model": "Next7P12",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "NextBook Tablets",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0; NXA8QC116 Build/LRX21V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "NextBook",
+ "model": "NXA8QC116",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Le Pan Tablets",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Le Pan TC802A Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Le Pan",
+ "model": "TC802A",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Le Pan Tablets",
+ "ua": "Mozilla/5.0 (Linux; Android 4.2.2; Le Pan TC802A Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
+ "expect": {
+ "vendor": "Le Pan",
+ "model": "TC802A",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "AT&T Radiant Core U304AA",
+ "ua": "Dalvik/2.1.0 (Linux; U; Android 9; U304AA Build/P00610)",
+ "expect": {
+ "vendor": "AT&T",
+ "model": "U304AA",
+ "type": "mobile"
+ }
+},
+{
+ "desc": "Vodafone Smart Tab 4G",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.4; Vodafone Smart Tab 4G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "expect": {
+ "vendor": "Vodafone",
+ "model": "Smart Tab 4G",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "Vodafone Smart ultra 6",
+ "ua": "Mozilla/5.0 (Linux; Android 5.0.2; Vodafone Smart ultra 6 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36",
+ "expect": {
+ "vendor": "Vodafone",
+ "model": "Smart ultra 6",
+ "type": "tablet"
+ }
+},
+{
+ "desc": "4ife 4K Smart TV Box",
+ "ua": "Mozilla/5.0 (Linux; Android 4.4.2; 4ife 4K Smart TV Box Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36 Vinebre",
+ "expect": {
+ "vendor": "undefined",
+ "model": "undefined",
+ "type": "smarttv"
+ }
+}]
\ No newline at end of file
diff --git a/test/data/ua/extension/fetcher.json b/test/data/ua/extension/fetcher.json
new file mode 100644
index 000000000..13c9f62b3
--- /dev/null
+++ b/test/data/ua/extension/fetcher.json
@@ -0,0 +1,402 @@
+[
+ {
+ "desc" : "AhrefsSiteAudit",
+ "ua" : "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Mobile Safari/537.36 (compatible; AhrefsSiteAudit/6.1; +http://ahrefs.com/robot/site-audit)",
+ "expect" :
+ {
+ "name" : "AhrefsSiteAudit",
+ "version" : "6.1",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Asana",
+ "ua" : "Asana/1.4.0 WebsiteMetadataRetriever",
+ "expect" :
+ {
+ "name" : "Asana",
+ "version" : "1.4.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Better Uptime Bot",
+ "ua" : "Better Uptime Bot Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Better Uptime Bot",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "BingPreview",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b",
+ "expect" :
+ {
+ "name" : "BingPreview",
+ "version" : "1.0b",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Bit.ly",
+ "ua" : "bitlybot/3.0 (+http://bit.ly/)",
+ "expect" :
+ {
+ "name" : "bitlybot",
+ "version" : "3.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Blueno",
+ "ua" : "acebookexternalhit/1.1 (compatible; Blueno/1.0; +http://naver.me/scrap)",
+ "expect" :
+ {
+ "name" : "Blueno",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Bluesky",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Bluesky Cardyb/1.1; +mailto:support@bsky.app) Chrome/100.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Bluesky",
+ "version" : "1.1",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "BufferLinkPreviewBot",
+ "ua" : "BufferLinkPreviewBot/1.0 (+https://scraper.buffer.com/about/bots/link-preview-bot)",
+ "expect" :
+ {
+ "name" : "BufferLinkPreviewBot",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "ChatGPT-User",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot",
+ "expect" :
+ {
+ "name" : "ChatGPT-User",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Chrome-Lighthouse",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4590.2 Mobile Safari/537.36 Chrome-Lighthouse",
+ "expect" :
+ {
+ "name" : "Chrome-Lighthouse",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "DuckAssistBot",
+ "ua" : "DuckAssistBot/1.2; (+http://duckduckgo.com/duckassistbot.html)",
+ "expect" :
+ {
+ "name" : "DuckAssistBot",
+ "version" : "1.2",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Gemini-Deep-Research",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Gemini-Deep-Research; +https://gemini.google/overview/deep-research/) Chrome/135.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Gemini-Deep-Research",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google FeedFetcher",
+ "ua" : "FeedFetcher-Google; (+http://www.google.com/feedfetcher.html)",
+ "expect" :
+ {
+ "name" : "FeedFetcher-Google",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google Image Proxy",
+ "ua" : "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)",
+ "expect" :
+ {
+ "name" : "GoogleImageProxy",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google Read Aloud - Mobile agent",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.0; SM-G930V Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)",
+ "expect" :
+ {
+ "name" : "Google-Read-Aloud",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google Read Aloud - Desktop agent",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 (compatible; Google-Read-Aloud; +https://support.google.com/webmasters/answer/1061943)",
+ "expect" :
+ {
+ "name" : "Google-Read-Aloud",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google Page Renderer",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 Google-PageRenderer Google (+https://developers.google.com/+/web/snippet/) ",
+ "expect" :
+ {
+ "name" : "Google-PageRenderer",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google Publisher Center",
+ "ua" : "GoogleProducer; (+https://developers.google.com/search/docs/crawling-indexing/google-producer)",
+ "expect" :
+ {
+ "name" : "GoogleProducer",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Google Site Verifier",
+ "ua" : "Mozilla/5.0 (compatible; Google-Site-Verification/1.0)",
+ "expect" :
+ {
+ "name" : "Google-Site-Verification",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Iframely",
+ "ua" : "Iframely/1.3.1 (+https://iframely.com/docs/about)",
+ "expect" :
+ {
+ "name" : "Iframely",
+ "version" : "1.3.1",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "kakaotalk-scrap",
+ "ua" : "facebookexternalhit/1.1; kakaotalk-scrap/1.0; +https://devtalk.kakao.com/t/scrap/33984",
+ "expect" :
+ {
+ "name" : "kakaotalk-scrap",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Meta-ExternalFetcher",
+ "ua" : "meta-externalfetcher/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)",
+ "expect" :
+ {
+ "name" : "meta-externalfetcher",
+ "version" : "1.1",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "MicrosoftPreview",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; MicrosoftPreview/2.0; +https://aka.ms/MicrosoftPreview) Chrome/W.X.Y.Z Safari/537.36",
+ "expect" :
+ {
+ "name" : "MicrosoftPreview",
+ "version" : "2.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "MistralAI-User",
+ "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; MistralAI-User/1.0; +https://docs.mistral.ai/robots)",
+ "expect" :
+ {
+ "name" : "MistralAI-User",
+ "version" : "1.0",
+ "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)",
+ "expect" :
+ {
+ "name" : "Perplexity-User",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Pinterestbot",
+ "ua" : "Mozilla/5.0 (compatible; Pinterestbot/1.0; +http://www.pinterest.com/bot.html)",
+ "expect" :
+ {
+ "name" : "Pinterestbot",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Rogerbot",
+ "ua" : "Mozilla/5.0 (compatible; rogerBot/1.0; UrlCrawler; http://www.seomoz.org/dp/rogerbot)",
+ "expect" :
+ {
+ "name" : "rogerBot",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "SiteAuditBot",
+ "ua" : "Mozilla/5.0 (compatible; SiteAuditBot/0.97; +http://www.semrush.com/bot.html)",
+ "expect" :
+ {
+ "name" : "SiteAuditBot",
+ "version" : "0.97",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Snap URL Preview",
+ "ua" : "Snap URL Preview Service; bot; snapchat; https://developers.snap.com/robots ",
+ "expect" :
+ {
+ "name" : "Snap URL Preview",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "SkypeUriPreview",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) SkypeUriPreview Preview/0.5 skype-url-preview@microsoft.com",
+ "expect" :
+ {
+ "name" : "SkypeUriPreview",
+ "version" : "0.5",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "TelegramBot",
+ "ua" : "TelegramBot (like TwitterBot)",
+ "expect" :
+ {
+ "name" : "TelegramBot",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "TikTokSpider",
+ "ua" : "Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; TikTokSpider; ttspider-feedback@tiktok.com)",
+ "expect" :
+ {
+ "name" : "TikTokSpider",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "UptimeRobot",
+ "ua" : "Mozilla/5.0 (compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)",
+ "expect" :
+ {
+ "name" : "UptimeRobot",
+ "version" : "2.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "vercel-favicon-bot",
+ "ua" : "vercel-favicon-bot",
+ "expect" :
+ {
+ "name" : "vercel-favicon-bot",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "vercel-screenshot-bot",
+ "ua" : "vercel-screenshot-bot",
+ "expect" :
+ {
+ "name" : "vercel-screenshot-bot",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Vercelbot",
+ "ua" : "Vercelbot (+https://vercel.com)",
+ "expect" :
+ {
+ "name" : "Vercelbot",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "vercelflags",
+ "ua" : "vercelflags",
+ "expect" :
+ {
+ "name" : "vercelflags",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "verceltracing",
+ "ua" : "verceltracing",
+ "expect" :
+ {
+ "name" : "verceltracing",
+ "version" : "undefined",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "WhatsApp",
+ "ua" : "WhatsApp/2.23.20.0",
+ "expect" :
+ {
+ "name" : "WhatsApp",
+ "version" : "2.23.20.0",
+ "type" : "fetcher"
+ }
+ },
+ {
+ "desc" : "Zoombot",
+ "ua" : "Mozilla/5.0 (compatible; Zoombot/1.0; +https://zoom.us; crawler@domain.com)",
+ "expect" :
+ {
+ "name" : "Zoombot",
+ "version" : "1.0",
+ "type" : "fetcher"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/extension/inapp.json b/test/data/ua/extension/inapp.json
new file mode 100644
index 000000000..f1861c401
--- /dev/null
+++ b/test/data/ua/extension/inapp.json
@@ -0,0 +1,202 @@
+[
+ {
+ "desc" : "Discord on Linux",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) discord/0.0.26 Chrome/108.0.5359.215 Electron/22.3.2 Safari/537.36",
+ "expect" :
+ {
+ "name" : "discord",
+ "version" : "0.0.26",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Discord on iPad",
+ "ua" : "Discord/52.0 (iPad; iOS 14.4; Scale/2.00)",
+ "expect" :
+ {
+ "name" : "Discord",
+ "version" : "52.0",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Evernote on Windows",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Evernote Windows/306387 (pt-PT, DDL); Windows/6.1.0 (Win32); Safari/537.36",
+ "expect" :
+ {
+ "name" : "Evernote",
+ "version" : "undefined",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Figma on Mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_4_0) AppleWebKit/537.36 (KHTML, like Gecko) Figma/99.0.0 Chrome/89.0.4389.128 Electron/12.0.9 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Figma",
+ "version" : "99.0.0",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Flipboard on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 8.0.0; SM-A720F Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.110 Mobile Safari/537.36 Flipboard/4.1.13/4342,4.1.13.4342",
+ "expect" :
+ {
+ "name" : "Flipboard",
+ "version" : "4.1.13",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Mattermost on Mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Mattermost/4.4.0 Chrome/76.0.3809.146 Electron/6.1.7 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Mattermost",
+ "version" : "4.4.0",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Mattermost on iPad",
+ "ua" : "Mattermost/1.49.1 (iPad; iOS 15.3.1; Scale/2.00)",
+ "expect" :
+ {
+ "name" : "Mattermost",
+ "version" : "1.49.1",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Microsoft Teams on Mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_3_1) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.8.00.3758 Chrome/126.0.6478.261 Electron/31.7.7 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Teams",
+ "version" : "1.8.00.3758",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Microsoft Teams on iPad",
+ "ua" : "Mozilla/5.0 (iPad; CPU OS 16_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 TeamsMobile-iOS",
+ "expect" :
+ {
+ "name" : "Teams",
+ "version" : "undefined",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Microsoft Teams on iPhone",
+ "ua" : "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 TeamsMobile-iOS",
+ "expect" :
+ {
+ "name" : "Teams",
+ "version" : "undefined",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Microsoft Teams on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 8.1.0; SM-G610F Build/M1AJQ; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.101 Mobile Safari/537.36 TeamsMobile-Android",
+ "expect" :
+ {
+ "name" : "Teams",
+ "version" : "undefined",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Notion on Mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Notion/2.0.23 Chrome/91.0.4472.164 Electron/13.6.9 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Notion",
+ "version" : "2.0.23",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Postman on Mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Postman/9.29.0 Chrome/94.0.4606.81 Electron/15.5.7 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Postman",
+ "version" : "9.29.0",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Rambox on mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Rambox/0.7.7 Chrome/78.0.3904.130 Electron/7.2.4 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Rambox",
+ "version" : "0.7.7",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Rocket.Chat on mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Rocket.Chat/3.8.16 Chrome/106.0.5249.199 Electron/21.3.3 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Rocket.Chat",
+ "version" : "3.8.16",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Slack on mac",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Slack/4.39.90 Chrome/127.0.6533.72 Electron/13.1.9 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Slack",
+ "version" : "4.39.90",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "TikTok Lite",
+ "ua" : "Mozilla/5.0 (Linux; Android 8.0.0; SM-J400F Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.91 Mobile Safari/537.36 Channel/release AppName/ultralite app_version/27.2.3 Region/ID ByteLocale/id-ID ByteFullLocale/id-ID",
+ "expect" :
+ {
+ "name" : "TikTok Lite",
+ "version" : "27.2.3",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "VS Code on Windows",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.85.1 Chrome/114.0.5735.289 Electron/25.9.7 Safari/537.36",
+ "expect" :
+ {
+ "name" : "VS Code",
+ "version" : "1.85.1",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Yahoo! Japan on Android",
+ "ua" : "Mozilla/5.0 (Linux; Android 13; SH-M20 Build/TKQ1.220915.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36 YJApp-ANDROID jp.co.yahoo.android.yjtop/3.187.0",
+ "expect" :
+ {
+ "name" : "Yahoo! Japan",
+ "version" : "3.187.0",
+ "type" : "inapp"
+ }
+ },
+ {
+ "desc" : "Yahoo! Japan on iOS",
+ "ua" : "Mozilla/5.0 (iPad; CPU OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 YJApp-IOS jp.co.yahoo.ipn.appli/4.131.0",
+ "expect" :
+ {
+ "name" : "Yahoo! Japan",
+ "version" : "4.131.0",
+ "type" : "inapp"
+ }
+ }
+]
diff --git a/test/data/ua/extension/library.json b/test/data/ua/extension/library.json
new file mode 100644
index 000000000..17435a28f
--- /dev/null
+++ b/test/data/ua/extension/library.json
@@ -0,0 +1,242 @@
+[
+ {
+ "desc" : "AdobeAIR",
+ "ua" : "Mozilla/5.0 (Windows; U; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) AdobeAIR/3.1",
+ "expect" :
+ {
+ "name" : "AdobeAIR",
+ "version" : "3.1",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "aiohttp",
+ "ua" : "Python/3.9 aiohttp/3.8.1",
+ "expect" :
+ {
+ "name" : "aiohttp",
+ "version" : "3.8.1",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Apache-HttpClient",
+ "ua" : "Apache-HttpClient/4.5.14 (Java/17.0.12)",
+ "expect" :
+ {
+ "name" : "Apache-HttpClient",
+ "version" : "4.5.14",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Axios",
+ "ua" : "axios/1.7.2",
+ "expect" :
+ {
+ "name" : "axios",
+ "version" : "1.7.2",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "go-http-client",
+ "ua" : "go-http-client/1.1",
+ "expect" :
+ {
+ "name" : "go-http-client",
+ "version" : "1.1",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "got",
+ "ua" : "got/9.6.0 (https://github.com/sindresorhus/got)",
+ "expect" :
+ {
+ "name" : "got",
+ "version" : "9.6.0",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "GuzzleHttp",
+ "ua" : "GuzzleHttp/6.5.5 curl/7.70.0 PHP/7.4.22",
+ "expect" :
+ {
+ "name" : "GuzzleHttp",
+ "version" : "6.5.5",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Java",
+ "ua" : "Java/1.6.0_14",
+ "expect" :
+ {
+ "name" : "Java",
+ "version" : "1.6.0_14",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Java HTTPClient",
+ "ua" : "Java-http-client/11.0.6",
+ "expect" :
+ {
+ "name" : "Java-http-client",
+ "version" : "11.0.6",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "jsdom",
+ "ua" : "Mozilla/5.0 (unknown OS) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/11.12.0",
+ "expect" :
+ {
+ "name" : "jsdom",
+ "version" : "11.12.0",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "libwww-perl",
+ "ua" : "libwww-perl/6.05",
+ "expect" :
+ {
+ "name" : "libwww-perl",
+ "version" : "6.05",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "lua-resty-http",
+ "ua" : "lua-resty-http/0.07 (Lua)",
+ "expect" :
+ {
+ "name" : "lua-resty-http",
+ "version" : "0.07",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Needle",
+ "ua" : "Needle/3.2.0 (Node.js v18.14.2; win32 x64)",
+ "expect" :
+ {
+ "name" : "Needle",
+ "version" : "3.2.0",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Nutch",
+ "ua" : "AliyunSecBot/Nutch-1.21-SNAPSHOT",
+ "expect" :
+ {
+ "name" : "Nutch",
+ "version" : "1.21-SNAPSHOT",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "OkHttp",
+ "ua" : "okhttp/3.2.0",
+ "expect" :
+ {
+ "name" : "okhttp",
+ "version" : "3.2.0",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "node-fetch",
+ "ua" : "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)",
+ "expect" :
+ {
+ "name" : "node-fetch",
+ "version" : "1.0",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "PHP-SOAP",
+ "ua" : "PHP-SOAP/7.4.33",
+ "expect" :
+ {
+ "name" : "PHP-SOAP",
+ "version" : "7.4.33",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "PostmanRuntime",
+ "ua" : "PostmanRuntime/7.26.5",
+ "expect" :
+ {
+ "name" : "PostmanRuntime",
+ "version" : "7.26.5",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Python httpx",
+ "ua" : "python-httpx/0.27.2",
+ "expect" :
+ {
+ "name" : "python-httpx",
+ "version" : "0.27.2",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Python urllib",
+ "ua" : "Python-urllib/2.6",
+ "expect" :
+ {
+ "name" : "Python-urllib",
+ "version" : "2.6",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Python urllib3",
+ "ua" : "python-urllib3/1.26.18",
+ "expect" :
+ {
+ "name" : "python-urllib3",
+ "version" : "1.26.18",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Python requests",
+ "ua" : "python-requests/2.32",
+ "expect" :
+ {
+ "name" : "python-requests",
+ "version" : "2.32",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "Scrapy",
+ "ua" : "Scrapy/1.5.0 (+https://scrapy.org)",
+ "expect" :
+ {
+ "name" : "Scrapy",
+ "version" : "1.5.0",
+ "type" : "library"
+ }
+ },
+ {
+ "desc" : "superagent",
+ "ua" : "node-superagent/5.0.2",
+ "expect" :
+ {
+ "name" : "node-superagent",
+ "version" : "5.0.2",
+ "type" : "library"
+ }
+ }
+]
diff --git a/test/mediaplayer-test.json b/test/data/ua/extension/mediaplayer.json
similarity index 94%
rename from test/mediaplayer-test.json
rename to test/data/ua/extension/mediaplayer.json
index d40ba04f0..d093048a3 100644
--- a/test/mediaplayer-test.json
+++ b/test/data/ua/extension/mediaplayer.json
@@ -24,9 +24,9 @@
"ua" : "AppleTV/3.0.2 (Macintosh; Intel Mac OS X 10.4.7) AppleWebKit/528.18",
"expect" :
{
- "name" : "WebKit",
- "version" : "528.18",
- "major" : "528"
+ "name" : "AppleTV",
+ "version" : "3.0.2",
+ "major" : "3"
}
},
{
@@ -109,16 +109,6 @@
"major" : "1"
}
},
- {
- "desc" : "Dalvik",
- "ua" : "Dalvik/1.2.0 (Linux; U; Android 2.2.1; GT-S5830L Build/FROYO)",
- "expect" :
- {
- "name" : "Dalvik",
- "version" : "1.2.0",
- "major" : "1"
- }
- },
{
"desc" : "NexPlayer",
"ua" : "E97510d/ Player/NexPlayer 4.0",
@@ -189,16 +179,6 @@
"major" : "3"
}
},
- {
- "desc" : "Java",
- "ua" : "Java/1.4.1_04",
- "expect" :
- {
- "name" : "Java",
- "version" : "1.4.1_04",
- "major" : "1"
- }
- },
{
"desc" : "LG Player",
"ua" : "LG Player 1.0; Android",
@@ -509,16 +489,6 @@
"major" : "3"
}
},
- {
- "desc" : "Wget",
- "ua" : "Wget/1.12 (darwin10.7.0)",
- "expect" :
- {
- "name" : "Wget",
- "version" : "1.12",
- "major" : "1"
- }
- },
{
"desc" : "Winamp",
"ua" : "Winamp 2.81",
diff --git a/test/data/ua/extension/vehicle.json b/test/data/ua/extension/vehicle.json
new file mode 100644
index 000000000..780005110
--- /dev/null
+++ b/test/data/ua/extension/vehicle.json
@@ -0,0 +1,62 @@
+[
+ {
+ "desc" : "BMW",
+ "ua" : "Mozilla/5.0 (Linux; Android 9; AFTLBT962E2) AppleWebKit/537.36 (KHTML, like Gecko) Silk/118.3.1 like Chrome/118.0.5993.155 Safari/537.36",
+ "expect" :
+ {
+ "vendor" : "BMW",
+ "model" : "undefined",
+ "type" : "undefined"
+ }
+ },
+ {
+ "desc" : "BYD",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; DiLink3.0 For BYD AUTO Build/QKQ1.200816.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Safari/537.36",
+ "expect" :
+ {
+ "vendor" : "BYD",
+ "model" : "undefined",
+ "type" : "undefined"
+ }
+ },
+ {
+ "desc" : "Jeep",
+ "ua" : "Mozilla/5.0 (Linux; Android 9; AFTLFT962X3) AppleWebKit/537.36 (KHTML, like Gecko) Silk/124.5.2 like Chrome/124.0.6367.248 Safari/537.36",
+ "expect" :
+ {
+ "vendor" : "Jeep",
+ "model" : "Wagooner",
+ "type" : "undefined"
+ }
+ },
+ {
+ "desc" : "Rivian",
+ "ua" : "Dalvik/2.1.0 (Linux; U; Android 10; Rivian R1T Build/QQ3A.200605.002)",
+ "expect" :
+ {
+ "vendor" : "Rivian",
+ "model" : "R1T",
+ "type" : "undefined"
+ }
+ },
+ {
+ "desc" : "Tesla",
+ "ua" : "Mozilla/5.0 (X11; GNU/Linux) AppleWebKit/537.36 (KHTML, like Gecko) Chromium/79.0.3945.130 Chrome/79.0.3945.130 Safari/537.36 Tesla/2020.36.16-3e9e4e8dd287",
+ "expect" :
+ {
+ "vendor" : "Tesla",
+ "model" : "undefined",
+ "type" : "embedded"
+ }
+ },
+ {
+ "desc" : "Volvo",
+ "ua" : "Mozilla/5.0 (VCC; 1.0; like Gecko) NetFront/4.2",
+ "expect" :
+ {
+ "vendor" : "Volvo",
+ "model" : "undefined",
+ "type" : "undefined"
+ }
+ }
+]
diff --git a/test/data/ua/os/aix.json b/test/data/ua/os/aix.json
new file mode 100644
index 000000000..d45d1cbe6
--- /dev/null
+++ b/test/data/ua/os/aix.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc" : "AIX",
+ "ua" : "Mozilla/5.0 (X11; U; AIX 000138384C00; en-US; rv:1.0.1) Gecko/20030213 Netscape/7.0",
+ "expect" :
+ {
+ "name" : "AIX",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "AIX",
+ "ua" : "Mozilla/5.0 (AIX; SPARC64; smart-thumbtack; Mosaic/2.7) Gecko/20100101 Mosaic/2.7",
+ "expect" :
+ {
+ "name" : "AIX",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "AIX",
+ "ua" : "Mozilla/5.0 (X11; N; Linux i686; en-US; rv:1.8.1.12) Gecko/20080219 Firefox/2.0.0.12 Mozilla/3.0 (X11; I; AIX 2) Navigator/9.0.0.6",
+ "expect" :
+ {
+ "name" : "AIX",
+ "version" : "2"
+ }
+ },
+ {
+ "desc" : "AIX",
+ "ua" : "Mozilla/5.0 (X11; U; AIX 7.2; en-US; rv:1.7.12) Gecko/20100101 Firefox/126.0",
+ "expect" :
+ {
+ "name" : "AIX",
+ "version" : "7.2"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/amigaos.json b/test/data/ua/os/amigaos.json
new file mode 100644
index 000000000..398d9b21b
--- /dev/null
+++ b/test/data/ua/os/amigaos.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "AmigaOS",
+ "ua" : "Mozilla/4.0 (compatible; AWEB 3.4 SE; AmigaOS)",
+ "expect" :
+ {
+ "name" : "AmigaOS",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/android-x86.json b/test/data/ua/os/android-x86.json
new file mode 100644
index 000000000..e83d3ca45
--- /dev/null
+++ b/test/data/ua/os/android-x86.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Android-x86",
+ "ua" : "Mozilla/5.0 (Linux; Android 7.1.2; Generic Android-x86) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 OPR/61.2.3076.56749",
+ "expect" :
+ {
+ "name" : "Android-x86",
+ "version" : "7.1.2"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/android.json b/test/data/ua/os/android.json
new file mode 100644
index 000000000..7304e1e7f
--- /dev/null
+++ b/test/data/ua/os/android.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "Android",
+ "ua" : "Mozilla/5.0 (Linux; U; Android 2.2.2; en-us; VM670 Build/FRG83G) AppleWebKit/533.1 (KHTML, like Gecko)",
+ "expect" :
+ {
+ "name" : "Android",
+ "version" : "2.2.2"
+ }
+ },
+ {
+ "desc" : "MIUI",
+ "ua" : "Dalvik/2.1.0 (Linux; U; Android 9; Mi MIX 3 5G MIUI/V10.3.2.0.PEMEUVF)",
+ "expect" :
+ {
+ "name" : "Android",
+ "version" : "9"
+ }
+ },
+ {
+ "desc" : "KTB-Nexus 5",
+ "ua" : "APP-My App/1.0 (Linux; Android 4.2.1; Nexus 5 Build/JOP40D)",
+ "expect" :
+ {
+ "name" : "Android",
+ "version" : "4.2.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/arcaos.json b/test/data/ua/os/arcaos.json
new file mode 100644
index 000000000..5097545b7
--- /dev/null
+++ b/test/data/ua/os/arcaos.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "ArcaOS",
+ "ua" : "Mozilla/5.0 (OS/2; ArcaOS 5.0.6; x86_64; rv:89.0) Gecko/20100101 Firefox/89.0",
+ "expect" :
+ {
+ "name" : "ArcaOS",
+ "version" : "5.0.6"
+ }
+ },
+ {
+ "desc" : "ArcaOS",
+ "ua" : "Mozilla/5.0 (OS/2; ArcaOS; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
+ "expect" :
+ {
+ "name" : "ArcaOS",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/arch.json b/test/data/ua/os/arch.json
new file mode 100644
index 000000000..a186af0f2
--- /dev/null
+++ b/test/data/ua/os/arch.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "Arch",
+ "ua" : "Mozilla/5.0 (X11; Arch Linux i686; rv:2.0) Gecko/20100101 Firefox/126.1",
+ "expect" :
+ {
+ "name" : "Arch",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Arch",
+ "ua" : "Mozilla/5.0 ArchLinux (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1",
+ "expect" :
+ {
+ "name" : "Arch",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Arch",
+ "ua" : "Uzbl (Webkit 1.1.10) (Arch Linux)",
+ "expect" :
+ {
+ "name" : "Arch",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/bada.json b/test/data/ua/os/bada.json
new file mode 100644
index 000000000..1125641a2
--- /dev/null
+++ b/test/data/ua/os/bada.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Bada",
+ "ua" : "Mozilla/5.0 (SAMSUNG; SAMSUNG-GT-S5253/S5253DDKC1; U; Bada/1.0; en-us) AppleWebKit/533.1 (KHTML, like Gecko) Dolfin/2.0 Mobile WQVGA SMM-MMS/1.2.0 OPN-B",
+ "expect" :
+ {
+ "name" : "Bada",
+ "version" : "1.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/beos.json b/test/data/ua/os/beos.json
new file mode 100644
index 000000000..045e54f93
--- /dev/null
+++ b/test/data/ua/os/beos.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "BeOS",
+ "ua" : "Mozilla/5.0 (BeOS; U; BeOS BePC; en-US; rv:1.8.1.8pre) Gecko/20070926 SeaMonkey/1.1.5pre",
+ "expect" :
+ {
+ "name" : "BeOS",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/blackberry.json b/test/data/ua/os/blackberry.json
new file mode 100644
index 000000000..1865ab6fb
--- /dev/null
+++ b/test/data/ua/os/blackberry.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "BlackBerry",
+ "ua" : "BlackBerry9300/5.0.0.912 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/378",
+ "expect" :
+ {
+ "name" : "BlackBerry",
+ "version" : "5.0.0.912"
+ }
+ },
+ {
+ "desc" : "BlackBerry 10",
+ "ua" : "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.3+ (KHTML, like Gecko) Version/10.0.9.386 Mobile Safari/537.3+",
+ "expect" :
+ {
+ "name" : "BlackBerry",
+ "version" : "10"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/centos.json b/test/data/ua/os/centos.json
new file mode 100644
index 000000000..4fe25b09a
--- /dev/null
+++ b/test/data/ua/os/centos.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "CentOS",
+ "ua" : "Konqueror/15.13 (CentOS Linux 7.4; cs-CZ;)",
+ "expect" :
+ {
+ "name" : "CentOS",
+ "version" : "7.4"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/chrome-os.json b/test/data/ua/os/chrome-os.json
new file mode 100644
index 000000000..69bc8786f
--- /dev/null
+++ b/test/data/ua/os/chrome-os.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Chrome OS",
+ "ua" : "Mozilla/5.0 (X11; CrOS x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Chrome OS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Chromium OS",
+ "ua" : "Mozilla/5.0 (X11; CrOS x86_64 10575.58.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Chrome OS",
+ "version" : "10575.58.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/chromecast-android.json b/test/data/ua/os/chromecast-android.json
new file mode 100644
index 000000000..c0969dd07
--- /dev/null
+++ b/test/data/ua/os/chromecast-android.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Google Chromecast with Google TV",
+ "ua" : "Mozilla/5.0 (Linux; Android 12.0; Build/STTL.240206.002) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.0 Safari/537.36 CrKey/1.56.500000 DeviceType/AndroidTV",
+ "expect" :
+ {
+ "name" : "Chromecast Android",
+ "version" : "12.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/chromecast-fuchsia.json b/test/data/ua/os/chromecast-fuchsia.json
new file mode 100644
index 000000000..5bfecf832
--- /dev/null
+++ b/test/data/ua/os/chromecast-fuchsia.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Google Chromecast Nest Hub",
+ "ua" : "Mozilla/5.0 (Fuchsia) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 CrKey/1.56.500000",
+ "expect" :
+ {
+ "name" : "Chromecast Fuchsia",
+ "version" : "1.56.500000"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/chromecast-linux.json b/test/data/ua/os/chromecast-linux.json
new file mode 100644
index 000000000..608234665
--- /dev/null
+++ b/test/data/ua/os/chromecast-linux.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Google Chromecast Legacy Linux-Based",
+ "ua" : "Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.81 Safari/537.36 CrKey/1.42.183786",
+ "expect" :
+ {
+ "name" : "Chromecast Linux",
+ "version" : "1.42.183786"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/chromecast-smartspeaker.json b/test/data/ua/os/chromecast-smartspeaker.json
new file mode 100644
index 000000000..eb6f15b46
--- /dev/null
+++ b/test/data/ua/os/chromecast-smartspeaker.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Google Chromecast Mini Smart Speaker",
+ "ua" : "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.225 Safari/537.36 CrKey/1.56.500000 DeviceType/SmartSpeaker",
+ "expect" :
+ {
+ "name" : "Chromecast SmartSpeaker",
+ "version" : "1.56.500000"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/contiki.json b/test/data/ua/os/contiki.json
new file mode 100644
index 000000000..b8b0503c3
--- /dev/null
+++ b/test/data/ua/os/contiki.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Contiki",
+ "ua" : "Contiki/1.0 (Commodore 64; http://dunkels.com/adam/contiki/)",
+ "expect" :
+ {
+ "name" : "Contiki",
+ "version" : "1.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/debian.json b/test/data/ua/os/debian.json
new file mode 100644
index 000000000..a6a777b2f
--- /dev/null
+++ b/test/data/ua/os/debian.json
@@ -0,0 +1,65 @@
+[
+ {
+ "desc" : "Debian",
+ "ua" : "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.7 (like Gecko) (Debian)",
+ "expect" :
+ {
+ "name" : "Debian",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Debian",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; Debian GNU/Linux 8.1 (jessie)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Debian",
+ "version" : "8.1"
+ }
+ },
+ {
+ "desc" : "Debian",
+ "ua" : "ELinks/0.12~pre5-4 (textmode; Debian; Linux 3.2.0-4-amd64 x86_64 192x47-2)",
+ "expect" :
+ {
+ "name" : "Debian",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Debian",
+ "ua" : "w3m/0.5.3+debian-19",
+ "expect" :
+ {
+ "name" : "debian",
+ "version" : "19"
+ }
+ },
+ {
+ "desc" : "Debian",
+ "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.3) Gecko/2008092814 (Debian-3.0.1-1)",
+ "expect" :
+ {
+ "name" : "Debian",
+ "version" : "3.0.1-1"
+ }
+ },
+ {
+ "desc" : "Debian",
+ "ua" : "Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.24.4; X11) KHTML/3.5.9 (like Gecko) (Debian package 4:3.5.9.dfsg.1-2+b1)",
+ "expect" :
+ {
+ "name" : "Debian",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Debian",
+ "ua" : "Mozilla/5.0 (X11; U; Linux sparc64; es-PY; rv:5.0) Gecko/20100101 IceCat/5.0 (like Firefox/5.0; Debian-6.0.1)",
+ "expect" :
+ {
+ "name" : "Debian",
+ "version" : "6.0.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/deepin.json b/test/data/ua/os/deepin.json
new file mode 100644
index 000000000..b41e2a3f7
--- /dev/null
+++ b/test/data/ua/os/deepin.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Deepin",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64; Deepin 15.5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36 NFSBrowser/5.0.0.1886",
+ "expect" :
+ {
+ "name" : "Deepin",
+ "version" : "15.5"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/dragonfly.json b/test/data/ua/os/dragonfly.json
new file mode 100644
index 000000000..f8f3c5833
--- /dev/null
+++ b/test/data/ua/os/dragonfly.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "DragonFly",
+ "ua" : "Mozilla/5.0 (X11; U; DragonFly i386; de; rv:1.9.1) Gecko/20090720 Firefox/3.5.1",
+ "expect" :
+ {
+ "name" : "DragonFly",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/elementary-os.json b/test/data/ua/os/elementary-os.json
new file mode 100644
index 000000000..1fd5db7b7
--- /dev/null
+++ b/test/data/ua/os/elementary-os.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "elementary OS",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/604.1 (KHTML, like Gecko) Version/11.0 Safari/604.1 elementary OS/0.4 (Loki) Epiphany/3.18.11",
+ "expect" :
+ {
+ "name" : "elementary OS",
+ "version" : "0.4"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/fedora.json b/test/data/ua/os/fedora.json
new file mode 100644
index 000000000..a7abe1fa4
--- /dev/null
+++ b/test/data/ua/os/fedora.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "Fedora",
+ "ua" : "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0",
+ "expect" :
+ {
+ "name" : "Fedora",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Fedora",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0",
+ "expect" :
+ {
+ "name" : "Fedora",
+ "version" : "16-dev"
+ }
+ },
+ {
+ "desc" : "Fedora",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; sk; rv:1.9.0.4) Gecko/2008111217 Fedora/3.0.4-1.fc10 Firefox/3.0.4",
+ "expect" :
+ {
+ "name" : "Fedora",
+ "version" : "3.0.4-1.fc10"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/firefox-os.json b/test/data/ua/os/firefox-os.json
new file mode 100644
index 000000000..faf081a15
--- /dev/null
+++ b/test/data/ua/os/firefox-os.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "Firefox OS",
+ "ua" : "Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0",
+ "expect" :
+ {
+ "name" : "Firefox OS",
+ "version" : "14.0"
+ }
+ },
+ {
+ "desc" : "Firefox OS on Tablet",
+ "ua" : "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0",
+ "expect" :
+ {
+ "name" : "Firefox OS",
+ "version" : "26.0"
+ }
+ },
+ {
+ "desc" : "Firefox OS on TV",
+ "ua" : "Mozilla/5.0 (TV; rv:44.0) Gecko/44.0 Firefox/44.0",
+ "expect" :
+ {
+ "name" : "Firefox OS",
+ "version" : "44.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/freebsd.json b/test/data/ua/os/freebsd.json
new file mode 100644
index 000000000..74544a0a7
--- /dev/null
+++ b/test/data/ua/os/freebsd.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "FreeBSD",
+ "ua" : "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16",
+ "expect" :
+ {
+ "name" : "FreeBSD",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/fuchsia.json b/test/data/ua/os/fuchsia.json
new file mode 100644
index 000000000..1f2e0501d
--- /dev/null
+++ b/test/data/ua/os/fuchsia.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Fuchsia",
+ "ua" : "Mozilla/5.0 (X11; Fuchsia x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3557.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Fuchsia",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Fuchsia",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Mozilla/5.0 (X11; Linux; Fuchsia; GoogleTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Large Screen Safari/537.36 GoogleTV",
+ "expect" :
+ {
+ "name" : "Fuchsia",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/gentoo.json b/test/data/ua/os/gentoo.json
new file mode 100644
index 000000000..8fa207ba6
--- /dev/null
+++ b/test/data/ua/os/gentoo.json
@@ -0,0 +1,39 @@
+[
+
+ {
+ "desc" : "Gentoo",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.16) Gecko/20080716 (Gentoo) Galeon/2.0.6",
+ "expect" :
+ {
+ "name" : "Gentoo",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Gentoo",
+ "ua" : "Xombrero (X11; U; Gentoo Linux amd64; en-US) Webkit/2.8.5",
+ "expect" :
+ {
+ "name" : "Gentoo",
+ "version" : "amd64"
+ }
+ },
+ {
+ "desc" : "Gentoo",
+ "ua" : "Xombrero/1.6.4 (Linux amd64; en; Gentoo)",
+ "expect" :
+ {
+ "name" : "Gentoo",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Gentoo",
+ "ua" : "Links (2.8; Linux 3.17.2-gentoo-x86 i686; GNU C 4.8.2; x)",
+ "expect" :
+ {
+ "name" : "gentoo",
+ "version" : "x86"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/ghostbsd.json b/test/data/ua/os/ghostbsd.json
new file mode 100644
index 000000000..5e36d81f8
--- /dev/null
+++ b/test/data/ua/os/ghostbsd.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "GhostBSD",
+ "ua" : "Mozilla/5.0 (X11; GhostBSD/10.3; x86_64; rv:50.0.1) Gecko/20100101 Firefox/50.0.1",
+ "expect" :
+ {
+ "name" : "GhostBSD",
+ "version" : "10.3"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/haiku.json b/test/data/ua/os/haiku.json
new file mode 100644
index 000000000..f2537395b
--- /dev/null
+++ b/test/data/ua/os/haiku.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc" : "Haiku",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Haiku R1 x86) AppleWebKit/602.1.1 (KHTML, like Gecko) WebPositive/1.2 Version/8.0 Safari/602.1.1",
+ "expect" :
+ {
+ "name" : "Haiku",
+ "version" : "R1"
+ }
+ },
+ {
+ "desc" : "Haiku",
+ "ua" : "Mozilla/5.0 (X11; Haiku x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
+ "expect" :
+ {
+ "name" : "Haiku",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Haiku",
+ "ua" : "Mozilla/5.0 (Haiku; BeOS; rv:99.0) Gecko/20100101 Firefox/99.0",
+ "expect" :
+ {
+ "name" : "Haiku",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Haiku",
+ "ua" : "Mozilla/5.0 (X11; Haiku BePC) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.15.17 Chrome/87.0.4280.144 Safari/537.36 Dooble/2023.12.25 Dooble/2023.12.25",
+ "expect" :
+ {
+ "name" : "Haiku",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/harmonyos.json b/test/data/ua/os/harmonyos.json
new file mode 100644
index 000000000..c2016541d
--- /dev/null
+++ b/test/data/ua/os/harmonyos.json
@@ -0,0 +1,56 @@
+[
+ {
+ "desc" : "HarmonyOS",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; HarmonyOS; YAL-AL10; HMSCore 6.3.0.327; GMSCore 21.48.15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/12.0.3.310 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "HarmonyOS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "HarmonyOS 2",
+ "ua" : "Mozilla/5.0 (Linux; Android 10; STK-AL00 Build/HUAWEISTK-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.93 Mobile Safari/537.36 BDOS/1.0 (HarmonyOS 2.2.0) SP-engine/2.72.0 baiduboxapp/13.34.5.10 (Baidu; P1 10) NABar/1.0",
+ "expect" :
+ {
+ "name" : "HarmonyOS",
+ "version" : "2.2.0"
+ }
+ },
+ {
+ "desc" : "HarmonyOS 3",
+ "ua" : "Mozilla/5.0 (Linux; Android 12; Huawei P60 Pro Build/HarmonyOS 3.1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.1.6136.139 Mobile Safari/537.36 OPR/111.11",
+ "expect" :
+ {
+ "name" : "HarmonyOS",
+ "version" : "3.1.0"
+ }
+ },
+ {
+ "desc" : "HarmonyOS 4",
+ "ua" : "Mozilla/5.0 (Android 12; HarmonyOS 4.0.0.118; OXF-AN00 Build/HUAWEIOXF-AN00; HMSCore 6.13.0.302; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36 EdgA/125.0.0.0 Language/zh_TW ABI/arm64-v8a",
+ "expect" :
+ {
+ "name" : "HarmonyOS",
+ "version" : "4.0.0.118"
+ }
+ },
+ {
+ "desc" : "HarmonyOS 4",
+ "ua" : "Mozilla/5.0 (Linux; Android 12; HarmonyOS/4.0.3.601; JAD-AL50; HMSCore/6.13.0.320 Build/AP31.240223.016.A3; x64; ARM64) AppleWebKit/537.36 (KHTML, like Gecko) HuaweiBrowser/14.0.7.302 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "HarmonyOS",
+ "version" : "4.0.3.601"
+ }
+ },
+ {
+ "desc" : "HarmonyOS 5",
+ "ua" : "Mozilla/5.0 (PC; OpenHarmony 5.0; HarmonyOS 5.0) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Browser/harmony360Browser/1.0.0",
+ "expect" :
+ {
+ "name" : "HarmonyOS",
+ "version" : "5.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/hp-ux.json b/test/data/ua/os/hp-ux.json
new file mode 100644
index 000000000..841474030
--- /dev/null
+++ b/test/data/ua/os/hp-ux.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "HP-UX",
+ "ua" : "Mozilla/5.0 (X11; U; HP-UX 9000/785; es-ES; rv:1.0.1) Gecko/20020827 Netscape/7.0",
+ "expect" :
+ {
+ "name" : "HP-UX",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/hurd.json b/test/data/ua/os/hurd.json
new file mode 100644
index 000000000..01707284c
--- /dev/null
+++ b/test/data/ua/os/hurd.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Hurd",
+ "ua" : "Mozilla/5.0 (X11; Hurd 0.9 i386; en-US) libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.7.0 Safari/696.96",
+ "expect" :
+ {
+ "name" : "Hurd",
+ "version" : "0.9"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/ios.json b/test/data/ua/os/ios.json
new file mode 100644
index 000000000..86ba8cb47
--- /dev/null
+++ b/test/data/ua/os/ios.json
@@ -0,0 +1,65 @@
+[
+ {
+ "desc" : "iOS in App",
+ "ua" : "AppName/version CFNetwork/version Darwin/version",
+ "expect" :
+ {
+ "name" : "iOS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "iOS with Chrome",
+ "ua" : "Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3",
+ "expect" :
+ {
+ "name" : "iOS",
+ "version" : "5.1.1"
+ }
+ },
+ {
+ "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",
+ "expect" :
+ {
+ "name" : "iOS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc": "iOS with FaceBook Mobile App",
+ "ua": "[FBAN/FBIOS;FBAV/283.0.0.44.117;FBBV/238386386;FBDV/iPhone12,1;FBMD/iPhone;FBSN/iOS;FBSV/13.6.1;FBSS/2;FBID/phone;FBLC/en_US;FBOP/5;FBRV/240127608]",
+ "expect":
+ {
+ "name" : "iOS",
+ "version" : "13.6.1"
+ }
+ },
+ {
+ "desc": "iOS with Slack App",
+ "ua": "com.tinyspeck.chatlyio/23.04.10 (iPhone; iOS 16.4.1; Scale/3.00)",
+ "expect":
+ {
+ "name" : "iOS",
+ "version" : "16.4.1"
+ }
+ },
+ {
+ "desc" : "iOS BE App",
+ "ua" : "APP-BE Test/1.0 (iPad; Apple; CPU iPhone OS 7_0_2 like Mac OS X)",
+ "expect" :
+ {
+ "name" : "iOS",
+ "version" : "7.0.2"
+ }
+ },
+ {
+ "desc": "Apple HomePod",
+ "ua": "AppleCoreMedia/1.0.0.15D61 (HomePod; U; CPU OS 11_2_5 like Mac OS X; en_us)",
+ "expect" :
+ {
+ "name" : "iOS",
+ "version" : "11.2.5"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/joli.json b/test/data/ua/os/joli.json
new file mode 100644
index 000000000..e9815fd8d
--- /dev/null
+++ b/test/data/ua/os/joli.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Joli",
+ "ua" : "Mozilla/5.0 (X11; Jolicloud Linux i686) AppleWebKit/537.6 (KHTML, like Gecko) Joli OS/1.2 Chromium/23.0.1240.0 Chrome/23.0.1240.0 Safari/537.6",
+ "expect" :
+ {
+ "name" : "Joli",
+ "version" : "1.2"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/kaios.json b/test/data/ua/os/kaios.json
new file mode 100644
index 000000000..f59572d59
--- /dev/null
+++ b/test/data/ua/os/kaios.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "KaiOS",
+ "ua" : "Mozilla/5.0 (Mobile; Nokia_8110_4G; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5",
+ "expect" :
+ {
+ "name" : "KAIOS",
+ "version" : "2.5"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/knoppix.json b/test/data/ua/os/knoppix.json
new file mode 100644
index 000000000..4ca6cdba1
--- /dev/null
+++ b/test/data/ua/os/knoppix.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Knoppix",
+ "ua" : "Mozilla/5.0 (Knoppix; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.3",
+ "expect" :
+ {
+ "name" : "Knoppix",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/kubuntu.json b/test/data/ua/os/kubuntu.json
new file mode 100644
index 000000000..d24214236
--- /dev/null
+++ b/test/data/ua/os/kubuntu.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Kubuntu",
+ "ua" : "Mozilla/5.0 (compatible; Konqueror/4.4; Linux 2.6.32-22-generic; X11; en_US) KHTML/4.4.3 (like Gecko) Kubuntu",
+ "expect" :
+ {
+ "name" : "Kubuntu",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Kubuntu",
+ "ua" : "Mozilla/5.0 (Kubuntu; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0",
+ "expect" :
+ {
+ "name" : "Kubuntu",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/linpus.json b/test/data/ua/os/linpus.json
new file mode 100644
index 000000000..40c81691c
--- /dev/null
+++ b/test/data/ua/os/linpus.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Linpus",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9b5pre) Gecko/2008032619 Linpus/3.0-0.49",
+ "expect" :
+ {
+ "name" : "Linpus",
+ "version" : "3.0-0.49"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/linspire.json b/test/data/ua/os/linspire.json
new file mode 100644
index 000000000..4d2497b24
--- /dev/null
+++ b/test/data/ua/os/linspire.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Linspire",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.4) Gecko/20060803 Firefox/1.5.0.4 Linspire/1.5.0.4",
+ "expect" :
+ {
+ "name" : "Linspire",
+ "version" : "1.5.0.4"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/linux.json b/test/data/ua/os/linux.json
new file mode 100644
index 000000000..af7859363
--- /dev/null
+++ b/test/data/ua/os/linux.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Linux",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Linux",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Linux",
+ "ua" : "Mozilla/5.0 (X11; U; Linux armv61; en-US; rv:1.9.1b2pre) Gecko/20081015 Fennec/1.0a1",
+ "expect" :
+ {
+ "name" : "Linux",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/macos.json b/test/data/ua/os/macos.json
new file mode 100644
index 000000000..43f90dba9
--- /dev/null
+++ b/test/data/ua/os/macos.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc" : "Mac OS on PowerPC",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 5.0b1; Mac_PowerPC)",
+ "expect" :
+ {
+ "name" : "macOS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Mac OS X on x86, x86_64, or aarch64 using Firefox",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:10.0) Gecko/20100101 Firefox/10.0",
+ "expect" :
+ {
+ "name" : "macOS",
+ "version" : "x.y"
+ }
+ },
+ {
+ "desc" : "Mac OS X on PowerPC using Firefox",
+ "ua" : "Mozilla/5.0 (Macintosh; PPC Mac OS X x.y; rv:10.0) Gecko/20100101 Firefox/10.0",
+ "expect" :
+ {
+ "name" : "macOS",
+ "version" : "x.y"
+ }
+ },
+ {
+ "desc" : "Mac OS",
+ "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36",
+ "expect" :
+ {
+ "name" : "macOS",
+ "version" : "10.6.8"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/maemo.json b/test/data/ua/os/maemo.json
new file mode 100644
index 000000000..9d898491f
--- /dev/null
+++ b/test/data/ua/os/maemo.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "Nokia N900 Linux mobile, on the Fennec browser",
+ "ua" : "Mozilla/5.0 (Maemo; Linux armv7l; rv:10.0) Gecko/20100101 Firefox/10.0 Fennec/10.0",
+ "expect" :
+ {
+ "name" : "Maemo",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Nokia N900 Linux mobile, on the Maemo browser",
+ "ua" : "Mozilla/5.0(X11; U; Linux armv7l; fr-FR; rv:1.9.2a1pre) Gecko/20091127 Firefox/3.5 Maemo Browser 1.5.6 RX-51 N900",
+ "expect" :
+ {
+ "name" : "Maemo",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Nokia N900 Linux mobile, on the Maemo browser",
+ "ua" : "Mozilla/5.0 (Linux; Maemo 5.0; Nokia N900; Build/4.0.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Maemo",
+ "version" : "5.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/mandriva.json b/test/data/ua/os/mandriva.json
new file mode 100644
index 000000000..3dbccf782
--- /dev/null
+++ b/test/data/ua/os/mandriva.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Mandriva",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.22) Gecko/20110907 Mandriva Linux/1.9.2.22-0.1mdv2010.2 (2010.2) Firefox/3.6.22",
+ "expect" :
+ {
+ "name" : "Mandriva",
+ "version" : "1.9.2.22-0.1mdv2010.2"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/manjaro.json b/test/data/ua/os/manjaro.json
new file mode 100644
index 000000000..ede015f22
--- /dev/null
+++ b/test/data/ua/os/manjaro.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Manjaro",
+ "ua" : "Mozilla/5.0 (X11; Manjaro 19.0.2; Arch; x64; rv:84.0) Gecko/20100101 Firefox/84.0",
+ "expect" :
+ {
+ "name" : "Manjaro",
+ "version" : "19.0.2"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/meego.json b/test/data/ua/os/meego.json
new file mode 100644
index 000000000..a506cde7a
--- /dev/null
+++ b/test/data/ua/os/meego.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "MeeGo",
+ "ua" : "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",
+ "expect" :
+ {
+ "name" : "MeeGo",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/minix.json b/test/data/ua/os/minix.json
new file mode 100644
index 000000000..d39a5148a
--- /dev/null
+++ b/test/data/ua/os/minix.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Minix",
+ "ua" : "Mozilla/5.0 (X11; Original ; Minix 3.3 ; rv:3.0)",
+ "expect" :
+ {
+ "name" : "Minix",
+ "version" : "3.3"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/mint.json b/test/data/ua/os/mint.json
new file mode 100644
index 000000000..8493d1ac2
--- /dev/null
+++ b/test/data/ua/os/mint.json
@@ -0,0 +1,128 @@
+[
+ {
+ "desc" : "Mint",
+ "ua" : "Opera/9.80 (X11; Linux x86_64; Edition Linux Mint) Presto/2.12.388 Version/12.16",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008121622 Linux Mint/6 (Felicia) Firefox/3.0.4",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "6"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008121622 Linux Mint/6 (Felicia) Firefox/3.0.4",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "6"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Opera/9.80 (X11; Linux i686; Edition Linux Mint) Presto/2.12.388 Version/12.15",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Opera/9.51 (X11; Linux i686; U; Linux Mint; en)",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; Linux Mint/19.3 x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Brave Chrome/83.0.4103.97 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "19.3"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; Linux Mint/20 x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Kiwi Chrome/100.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "20"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; Linux Mint/20.2; x86_64) Gecko/20100101 Firefox/92.0.1",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "20.2"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.0.10) Gecko/2009042513 Linux Mint/5 (Elyssa) Firefox/3.0.10",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "5"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.11) Gecko/2009060308 Linux Mint/7 (Gloria) Firefox/54.0",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20091020 Linux Mint/8 (Helena) Firefox/3.5.3",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "8"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:2.0) Gecko/20100101 Linux Mint 16/Petra Firefox/25.0.1.",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "16"
+ }
+ },
+ {
+ "desc" : "Mint",
+ "ua" : "Mozilla/5.0 (Linux x86_64; Mint 21.3) AppleWebKit/537.36 (KHTML, like Gecko) Brave/128.0.0.0 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Mint",
+ "version" : "21.3"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/morphos.json b/test/data/ua/os/morphos.json
new file mode 100644
index 000000000..e540858d9
--- /dev/null
+++ b/test/data/ua/os/morphos.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc" : "MorphOS",
+ "ua" : "AmigaVoyager/3.4.4 (MorphOS/PPC native)",
+ "expect" :
+ {
+ "name" : "MorphOS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "MorphOS",
+ "ua" : "Mozilla/5.0 (Macintosh; PowerPC MorphOS 3.7; Odyssey Web Browser; rv:1.23) AppleWebKit/538.1 (KHTML, like Gecko) OWB/1.23 Safari/538.1",
+ "expect" :
+ {
+ "name" : "MorphOS",
+ "version" : "3.7"
+ }
+ },
+ {
+ "desc" : "MorphOS",
+ "ua" : "Mozilla/5.0 (X11; MorphOS ppc64; rv:88.0) Gecko/20100101 Firefox/88.0",
+ "expect" :
+ {
+ "name" : "MorphOS",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "MorphOS",
+ "ua" : "Mozilla/5.0 (compatible; Origyn Web Browser; MorphOS; PPC; U) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+)",
+ "expect" :
+ {
+ "name" : "MorphOS",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/netbsd.json b/test/data/ua/os/netbsd.json
new file mode 100644
index 000000000..3a7657a27
--- /dev/null
+++ b/test/data/ua/os/netbsd.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "NetBSD",
+ "ua" : "ELinks (0.4.3; NetBSD 3.0.2PATCH sparc64; 141x19)",
+ "expect" :
+ {
+ "name" : "NetBSD",
+ "version" : "3.0.2PATCH"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/netrange.json b/test/data/ua/os/netrange.json
new file mode 100644
index 000000000..a7ff0a988
--- /dev/null
+++ b/test/data/ua/os/netrange.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Netrange Smart TV",
+ "ua" : "Mozilla/5.0 (Linux; U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 OPR/46.0.2207.0 LOEWE-SL410/5.2.0.0 HbbTV/1.4.1 (; LOEWE; SL410; LOH/5.2.0.0;;) FVC/3.0 (LOEWE; SL410;) CE-HTML/1.0 Config (L:deu,CC:DEU) NETRANGEMMH",
+ "expect" :
+ {
+ "name" : "NETRANGE",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/nettv.json b/test/data/ua/os/nettv.json
new file mode 100644
index 000000000..2584e4096
--- /dev/null
+++ b/test/data/ua/os/nettv.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "NetTV 3.2.1",
+ "ua" : "Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.2.1; en) Presto/2.6.33 Version/10.70",
+ "expect" :
+ {
+ "name" : "NETTV",
+ "version" : "3.2.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/nintendo.json b/test/data/ua/os/nintendo.json
new file mode 100644
index 000000000..b8ac8a398
--- /dev/null
+++ b/test/data/ua/os/nintendo.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Nintendo Switch",
+ "ua" : "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/606.4 (KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393",
+ "expect" :
+ {
+ "name" : "Nintendo",
+ "version" : "Switch"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/openbsd.json b/test/data/ua/os/openbsd.json
new file mode 100644
index 000000000..25164de54
--- /dev/null
+++ b/test/data/ua/os/openbsd.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "OpenBSD",
+ "ua" : "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.1) Gecko/20090702 Firefox/3.5",
+ "expect" :
+ {
+ "name" : "OpenBSD",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/openharmony.json b/test/data/ua/os/openharmony.json
new file mode 100644
index 000000000..b0606a1c4
--- /dev/null
+++ b/test/data/ua/os/openharmony.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "OpenHarmony",
+ "ua" : "Mozilla/5.0 (Phone; OpenHarmony 4.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile",
+ "expect" :
+ {
+ "name" : "OpenHarmony",
+ "version" : "4.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/os2.json b/test/data/ua/os/os2.json
new file mode 100644
index 000000000..56eac7c3f
--- /dev/null
+++ b/test/data/ua/os/os2.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "OS/2",
+ "ua" : "Links (2.1pre14; OS/2 1 i386; 80x33)",
+ "expect" :
+ {
+ "name" : "OS/2",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "OS/2",
+ "ua" : "Mozilla/5.0 (OS/2; OS/2 i386) AppleWebKit/538.36 (KHTML, like Gecko) QtWebEngine/5.15.2 Chrome/127.0.6533.72",
+ "expect" :
+ {
+ "name" : "OS/2",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "OS/2",
+ "ua" : "Mozilla/5.0 (OS/2; Warp 4.5; rv:45.0) Gecko/20100101 Firefox/45.0",
+ "expect" :
+ {
+ "name" : "OS/2",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/palm.json b/test/data/ua/os/palm.json
new file mode 100644
index 000000000..5fc831ebc
--- /dev/null
+++ b/test/data/ua/os/palm.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Palm OS",
+ "ua" : "Mozilla/4.76 [en] (PalmOS; U; WebPro3.0; Palm-Arz1)",
+ "expect" :
+ {
+ "name" : "Palm",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/pclinuxos.json b/test/data/ua/os/pclinuxos.json
new file mode 100644
index 000000000..521b4be9b
--- /dev/null
+++ b/test/data/ua/os/pclinuxos.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "PCLinuxOS",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) Gecko/20101209 PCLinuxOS/1.9.2.13-1pclos2010 (2010) Firefox/3.6.13",
+ "expect" :
+ {
+ "name" : "PCLinuxOS",
+ "version" : "1.9.2.13-1pclos2010"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/pico.json b/test/data/ua/os/pico.json
new file mode 100644
index 000000000..456a5fa47
--- /dev/null
+++ b/test/data/ua/os/pico.json
@@ -0,0 +1,26 @@
+[
+ {
+ "desc": "Pico 4",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; PICO 4 OS5.8.2 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.38 Chrome/105.0.5195.68 VR Safari/537.36",
+ "expect": {
+ "name" : "PICO",
+ "version" : "5.8.2"
+ }
+ },
+ {
+ "desc": "Pico 4",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; PICO 4 OS5.4.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.22 Chrome/105.0.5195.68 VR Safari/537.36 OculusBrowser/7.0",
+ "expect": {
+ "name" : "PICO",
+ "version" : "5.4.0"
+ }
+ },
+ {
+ "desc": "Pico Neo3 Link",
+ "ua": "Mozilla/5.0 (X11; Linux x86_64; Pico Neo3 Link OS5.8.4.0 like Quest) AppleWebKit/537.36 (KHTML, like Gecko) PicoBrowser/3.3.22 Chrome/105.0.5195.68 VR Safari/537.36",
+ "expect": {
+ "name" : "Pico",
+ "version" : "5.8.4.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/plan9.json b/test/data/ua/os/plan9.json
new file mode 100644
index 000000000..e3726fb9a
--- /dev/null
+++ b/test/data/ua/os/plan9.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Plan9",
+ "ua" : "NCSA_Mosaic/5.0 (X11;Plan 9 4.0)",
+ "expect" :
+ {
+ "name" : "Plan 9",
+ "version" : "4.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/playstation.json b/test/data/ua/os/playstation.json
new file mode 100644
index 000000000..38cf97fe4
--- /dev/null
+++ b/test/data/ua/os/playstation.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "PlayStation 4",
+ "ua" : "Mozilla/5.0 (PlayStation 4 3.00) AppleWebKit/537.73 (KHTML, like Gecko)",
+ "expect" :
+ {
+ "name" : "PlayStation",
+ "version" : "4"
+ }
+ },
+ {
+ "desc" : "PlayStation 5",
+ "ua" : "Mozilla/5.0 (PlayStation 5/SmartTV) AppleWebKit/605.1.15 (KHTML, like Gecko)",
+ "expect" :
+ {
+ "name" : "PlayStation",
+ "version" : "5"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/qnx.json b/test/data/ua/os/qnx.json
new file mode 100644
index 000000000..2817e488f
--- /dev/null
+++ b/test/data/ua/os/qnx.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "QNX",
+ "ua" : "Mozilla/5.0 (Photon; U; QNX x86pc; en-US; rv:1.8.1.20) Gecko/20090127 BonEcho/2.0.0.20",
+ "expect" :
+ {
+ "name" : "QNX",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/raspbian.json b/test/data/ua/os/raspbian.json
new file mode 100644
index 000000000..10fd9fb11
--- /dev/null
+++ b/test/data/ua/os/raspbian.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Raspbian",
+ "ua" : "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/72.0.3626.121 HeadlessChrome/72.0.3626.121 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Raspbian",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Raspbian",
+ "ua" : "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/538.15 (KHTML, like Gecko) Version/8.0 Safari/538.15 Raspbian/9.0 (1:3.8.2.0-0rpi28) Epiphany/3.8.2",
+ "expect" :
+ {
+ "name" : "Raspbian",
+ "version" : "9.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/redhat.json b/test/data/ua/os/redhat.json
new file mode 100644
index 000000000..89f3e25bd
--- /dev/null
+++ b/test/data/ua/os/redhat.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc" : "RedHat",
+ "ua" : "Mozilla/5.0 (compatible; Konqueror/4.3; Linux) KHTML/4.3.4 (like Gecko) Red Hat Enterprise Linux/4.3.4-11.el6_1.4",
+ "expect" :
+ {
+ "name" : "Red Hat",
+ "version" : "4.3.4-11.el6_1.4"
+ }
+ },
+ {
+ "desc" : "RedHat",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.13pre) Gecko/20070717 Red Hat/1.0.9-4.el4 SeaMonkey/1.0.9",
+ "expect" :
+ {
+ "name" : "Red Hat",
+ "version" : "1.0.9-4.el4"
+ }
+ },
+ {
+ "desc" : "RedHat",
+ "ua" : "iTunes/4.7.1 (Linux; N; Red Hat; x86_64-linux; EN; utf8) SqueezeCenter, Squeezebox Server, Logitech Media Server/7.9.1/1522157629",
+ "expect" :
+ {
+ "name" : "Red Hat",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "RedHat",
+ "ua" : "curl/7.20.0 (x86_64-redhat-linux-gnu) libcurl/7.20.0 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5",
+ "expect" :
+ {
+ "name" : "redhat",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/rim-tablet-os.json b/test/data/ua/os/rim-tablet-os.json
new file mode 100644
index 000000000..4451c3cda
--- /dev/null
+++ b/test/data/ua/os/rim-tablet-os.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "RIM Tablet OS",
+ "ua" : "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+",
+ "expect" :
+ {
+ "name" : "RIM Tablet OS",
+ "version" : "2.1.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/risc-os.json b/test/data/ua/os/risc-os.json
new file mode 100644
index 000000000..5442784e3
--- /dev/null
+++ b/test/data/ua/os/risc-os.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "RISC OS",
+ "ua" : "Mozilla/1.10 [en] (Compatible; RISC OS 3.70; Oregano 1.10)",
+ "expect" :
+ {
+ "name" : "RISC OS",
+ "version" : "3.70"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/sabayon.json b/test/data/ua/os/sabayon.json
new file mode 100644
index 000000000..d44f9e2d3
--- /dev/null
+++ b/test/data/ua/os/sabayon.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Sabayon",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Sabayon Chrome/19.0.1084.46 Safari/536.5",
+ "expect" :
+ {
+ "name" : "Sabayon",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/sailfish.json b/test/data/ua/os/sailfish.json
new file mode 100644
index 000000000..f96376be4
--- /dev/null
+++ b/test/data/ua/os/sailfish.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Sailfish",
+ "ua" : "Mozilla/5.0 (Linux; U; Sailfish 3.0; Mobile; rv:45.0) Gecko/45.0 Firefox/45.0 SailfishBrowser/1.0",
+ "expect" :
+ {
+ "name" : "Sailfish",
+ "version" : "3.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/serenityos.json b/test/data/ua/os/serenityos.json
new file mode 100644
index 000000000..748546e39
--- /dev/null
+++ b/test/data/ua/os/serenityos.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "SerenityOS",
+ "ua" : "Mozilla/4.0 (SerenityOS; x86) LibWeb+LibJS (Not KHTML, nor Gecko) LibWeb",
+ "expect" :
+ {
+ "name" : "SerenityOS",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/series40.json b/test/data/ua/os/series40.json
new file mode 100644
index 000000000..1b02c1538
--- /dev/null
+++ b/test/data/ua/os/series40.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Series40",
+ "ua" : "Mozilla/5.0 (Series40; Nokia2055/03.20; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/2.2.0.0.34",
+ "expect" :
+ {
+ "name" : "Series40",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/slackware.json b/test/data/ua/os/slackware.json
new file mode 100644
index 000000000..35f446e69
--- /dev/null
+++ b/test/data/ua/os/slackware.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Slackware",
+ "ua" : "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41",
+ "expect" :
+ {
+ "name" : "Slackware",
+ "version" : "13.37"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/solaris.json b/test/data/ua/os/solaris.json
new file mode 100644
index 000000000..4c8f6739e
--- /dev/null
+++ b/test/data/ua/os/solaris.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc" : "Solaris",
+ "ua" : "Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:1.7) Gecko/20070606",
+ "expect" :
+ {
+ "name" : "Solaris",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Solaris",
+ "ua" : "Mozilla/5.0 (Solaris; SPARC; w3m/0.5.3; rv:1.0) Gecko/20100101 w3m/0.5.3",
+ "expect" :
+ {
+ "name" : "Solaris",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Solaris",
+ "ua" : "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)",
+ "expect" :
+ {
+ "name" : "Solaris",
+ "version" : "4.1.4"
+ }
+ },
+ {
+ "desc" : "Solaris",
+ "ua" : "Opera/5.0 (SunOS 5.8 sun4m; U) [en]",
+ "expect" :
+ {
+ "name" : "Solaris",
+ "version" : "5.8"
+ }
+ },
+ {
+ "desc" : "Solaris",
+ "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.8) Gecko/20100215 Solaris/10.1 (GNU) Superswan/3.5.8 (Byte/me)",
+ "expect" :
+ {
+ "name" : "Solaris",
+ "version" : "10.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/suse.json b/test/data/ua/os/suse.json
new file mode 100644
index 000000000..57f5f6989
--- /dev/null
+++ b/test/data/ua/os/suse.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "OpenSUSE",
+ "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.17) Gecko/20110420 SUSE/3.6.17-0.2.1 Firefox/3.6.17",
+ "expect" :
+ {
+ "name" : "SUSE",
+ "version" : "3.6.17-0.2.1"
+ }
+ },
+ {
+ "desc" : "SUSE",
+ "ua" : "Mozilla/5.0 (X11; SUSE; Linux amd64; rv:86.0) Gecko/20100101 Firefox/86.0",
+ "expect" :
+ {
+ "name" : "SUSE",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/symbian.json b/test/data/ua/os/symbian.json
new file mode 100644
index 000000000..0e61d0281
--- /dev/null
+++ b/test/data/ua/os/symbian.json
@@ -0,0 +1,65 @@
+[
+ {
+ "desc" : "Nokia 5250",
+ "ua" : "Nokia5250/10.0.011 (SymbianOS/9.4; U; Series60/5.0 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/525 (KHTML, like Gecko) Safari/525 3gpp-gba",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "9.4"
+ }
+ },
+ {
+ "desc" : "Nokia N79",
+ "ua" : "Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 NokiaN79-1/32.001; Profile/MIDP-2.1 Configuration/CLDC-1",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "9.3"
+ }
+ },
+ {
+ "desc" : "Nokia E71",
+ "ua" : "Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaE71-1/110.07.127; Profile/MIDP-2.0 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "9.2"
+ }
+ },
+ {
+ "desc" : "Opera Mini on S60",
+ "ua" : "Opera/9.80 (Series 60; Opera Mini/7.1.32444/191.361; U; de) Presto/2.12.423 Version/12.16",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "NokiaBrowser on Nokia C7",
+ "ua" : "Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaC7-00/024.001; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.1.37 Mobile Safari/533.4 3gpp-gba",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "3"
+ }
+ },
+ {
+ "desc" : "Nokia 808 PureView",
+ "ua" : "Mozilla/5.0 (Symbian/3; Series60/5.5 Nokia808PureView/113.010.1508; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/535.1 (KHTML, like Gecko) NokiaBrowser/8.3.2.21 Mobile Safari/535.1 3gpp-gba",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "3"
+ }
+ },
+ {
+ "desc" : "Nokia 808 PureView",
+ "ua" : "Mozilla/5.0 (Symbian; U; Nokia808 PureView; en-GB) AppleWebKit/534.3 (KHTML, like Gecko) Version/3.0 Mobile/1A543a Mobile Safari/534.3",
+ "expect" :
+ {
+ "name" : "Symbian",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/tizen.json b/test/data/ua/os/tizen.json
new file mode 100644
index 000000000..4f9320d7c
--- /dev/null
+++ b/test/data/ua/os/tizen.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "Tizen",
+ "ua" : "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1",
+ "expect" :
+ {
+ "name" : "Tizen",
+ "version" : "2.3"
+ }
+ },
+ {
+ "desc" : "Tizen",
+ "ua" : "Mozilla/5.0 (Linux; Tizen 2.3; SAMSUNG SM-Z130H) AppleWebKit/537.3 (KHTML, like Gecko) Version/2.3 Mobile Safari/537.3",
+ "expect" :
+ {
+ "name" : "Tizen",
+ "version" : "2.3"
+ }
+ },
+ {
+ "desc" : "Tizen 6.0",
+ "ua" : "HbbTV/1.5.1 (+DRM;Samsung;SmartTV2021:UAU7000;T-KSU2EDEUC-1506.0;KantSU2e;urn:samsungtv:familyname:21_KANTSU2E_UHD_BASIC:2021;) Tizen/6.0 (+TVPLUS+SmartHubLink) Chrome/76 LaTivu_1.0.1_2021 RVID/17",
+ "expect" :
+ {
+ "name" : "Tizen",
+ "version" : "6.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/ubuntu-touch.json b/test/data/ua/os/ubuntu-touch.json
new file mode 100644
index 000000000..e0aea5ff2
--- /dev/null
+++ b/test/data/ua/os/ubuntu-touch.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Ubuntu Touch",
+ "ua" : "Mozilla/5.0 (Linux; Ubuntu 16.04 like Android 4.4) AppleWebKit/537.36 Chromium/65.0.3325.151 Mobile Safari/537.36",
+ "expect" :
+ {
+ "name" : "Ubuntu Touch",
+ "version" : "16.04"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/ubuntu.json b/test/data/ua/os/ubuntu.json
new file mode 100644
index 000000000..5438e8934
--- /dev/null
+++ b/test/data/ua/os/ubuntu.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc" : "Ubuntu",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.22+ (KHTML, like Gecko) Chromium/17.0.963.56 Chrome/17.0.963.56 Safari/535.22+ Ubuntu/12.04 (3.4.1-0ubuntu1) Epiphany/3.4.1",
+ "expect" :
+ {
+ "name" : "Ubuntu",
+ "version" : "12.04"
+ }
+ },
+ {
+ "desc" : "Ubuntu",
+ "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/31.0.1650.63 Chrome/31.0.1650.63 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Ubuntu",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Ubuntu",
+ "ua" : "Mozilla/5.0 (Wayland; Linux x86_64; Huawei) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Ubuntu/23.04 Edg/110.0.1587.41",
+ "expect" :
+ {
+ "name" : "Ubuntu",
+ "version" : "23.04"
+ }
+ },
+ {
+ "desc" : "Ubuntu",
+ "ua" : "Mozilla/5.0 (X11; Ubuntu 20.04; Linux arm; rv:99.0) Gecko/20100101 Firefox/99.0",
+ "expect" :
+ {
+ "name" : "Ubuntu",
+ "version" : "20.04"
+ }
+ },
+ {
+ "desc" : "Ubuntu",
+ "ua" : "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
+ "expect" :
+ {
+ "name" : "Ubuntu",
+ "version" : "14.10"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/unix.json b/test/data/ua/os/unix.json
new file mode 100644
index 000000000..18e733208
--- /dev/null
+++ b/test/data/ua/os/unix.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "UNIX",
+ "ua" : "Surf/0.4.1 (X11; U; Unix; en-US) AppleWebKit/531.2+ Compatible (Safari)",
+ "expect" :
+ {
+ "name" : "Unix",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/watchos.json b/test/data/ua/os/watchos.json
new file mode 100644
index 000000000..ec2c30b55
--- /dev/null
+++ b/test/data/ua/os/watchos.json
@@ -0,0 +1,29 @@
+[
+ {
+ "desc" : "watchOS",
+ "ua" : "server-bag [Watch OS,8.4,19S546,Watch3,4]",
+ "expect" :
+ {
+ "name" : "watchOS",
+ "version" : "8.4"
+ }
+ },
+ {
+ "desc" : "watchOS",
+ "ua" : "atc/1.0 watchOS/7.4.1 model/Watch3,3 hwp/t8004 build/18T201 (6; dt:155)",
+ "expect" :
+ {
+ "name" : "watchOS",
+ "version" : "7.4.1"
+ }
+ },
+ {
+ "desc" : "watchOS",
+ "ua" : "Watch4,3/5.3.8 (16U680)",
+ "expect" :
+ {
+ "name" : "watchOS",
+ "version" : "5.3.8"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/webos.json b/test/data/ua/os/webos.json
new file mode 100644
index 000000000..7f3d04ad4
--- /dev/null
+++ b/test/data/ua/os/webos.json
@@ -0,0 +1,119 @@
+[
+ {
+ "desc" : "HP WebOS",
+ "ua" : "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.5; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.83 Safari/534.6 TouchPad/1.0",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "3.0.5"
+ }
+ },
+ {
+ "desc" : "Palm WebOS",
+ "ua" : "Mozilla/5.0 (webOS/1.4.5; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "1.4.5"
+ }
+ },
+ {
+ "desc" : "WebOS TV 22",
+ "ua" : " Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chr0me/87.0.4280.88 Safari/537.36 LG Browser/8.00.00(LGE; ST50K-LG2200WEB; 04.42.26; 0x00000001; DTV_C22P); webOS.TV-2022; LG NetCast.TV-2013 Compatible (LGE, ST50K-LG2200WEB, wireless)",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "22"
+ }
+ },
+ {
+ "desc" : "WebOS TV 22",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 HbbTV/1.6.1 (+DRM; LGE/SILICONPLAYER/Hyundai; 50HYN3205; WEBOS22 04.42.26; W22_K8LP; DTV_C22P;)",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "22"
+ }
+ },
+ {
+ "desc" : "WebOS TV 5.0",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chr0me/68.0.3440.106 Safari/537.36 LG Browser/8.00.00(LGE; SWU-6522; 04.50.63; 0x00000001; DTV_C20P); webOS.TV-2020; LG NetCast.TV-2013 Compatible (LGE, SWU-6522, wireless)",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "5"
+ }
+ },
+ {
+ "desc" : "WebOS TV 5.0",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 HbbTV/1.5.1 (+DRM; LGE/WALTON/Hyundai; 50HYN3205; WEBOS5.0 04.50.63; W50_K6LP; DTV_C20P;)",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "5.0"
+ }
+ },
+ {
+ "desc" : "WebOS TV 5.x",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "5"
+ }
+ },
+ {
+ "desc" : "WebOS TV 5.x",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chr0me/68.0.3440.106 Safari/537.36 LG Browser/8.00.00(LGE; E65A71B-S; 04.50.63; 0x00000001; DTV_C20P); webOS.TV-2020; LG NetCast.TV-2013 Compatible (LGE, E65A71B-S, wired)",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "5"
+ }
+ },
+ {
+ "desc" : "WebOS TV 4.x",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "4"
+ }
+ },
+ {
+ "desc" : "WebOS TV 3.5",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 HbbTV/1.3.1 ( DRM; LGE; OLED55B7V-T; WEBOS3.5 06.10.60; W3_M16P; ) FVC/2.0 (LGE; WEBOS3.5 ;)",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "3.5"
+ }
+ },
+ {
+ "desc" : "WebOS TV 3.x",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "3"
+ }
+ },
+ {
+ "desc" : "WebOS TV 2.x",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/538.2 (KHTML, like Gecko) Large Screen WebAppManager Safari/538.2",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "2"
+ }
+ },
+ {
+ "desc" : "WebOS TV 1.x",
+ "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen WebAppManager Safari/537.41",
+ "expect" :
+ {
+ "name" : "webOS",
+ "version" : "1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/windows-ce.json b/test/data/ua/os/windows-ce.json
new file mode 100644
index 000000000..a28eb17fd
--- /dev/null
+++ b/test/data/ua/os/windows-ce.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Windows CE",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11)",
+ "expect" :
+ {
+ "name" : "Windows CE",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Windows CE",
+ "ua" : "Mozilla/4.0 (PDA; Windows CE/1.0.1) NetFront/3.0",
+ "expect" :
+ {
+ "name" : "Windows CE",
+ "version" : "1.0.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/windows-iot.json b/test/data/ua/os/windows-iot.json
new file mode 100644
index 000000000..ff1d02c05
--- /dev/null
+++ b/test/data/ua/os/windows-iot.json
@@ -0,0 +1,11 @@
+[
+ {
+ "desc" : "Windows IoT",
+ "ua" : "Mozilla/5.0 (Windows IoT 10.0; Android 6.0.1; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Mobile Safari/537.36 Edge/18.17763",
+ "expect" :
+ {
+ "name" : "Windows IoT",
+ "version" : "10.0"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/windows-mobile.json b/test/data/ua/os/windows-mobile.json
new file mode 100644
index 000000000..78df88b1a
--- /dev/null
+++ b/test/data/ua/os/windows-mobile.json
@@ -0,0 +1,38 @@
+[
+ {
+ "desc" : "Windows Mobile",
+ "ua" : "Mozilla/5.0 (ZTE-E_N72/N72V1.0.0B02;U;Windows Mobile/6.1;Profile/MIDP-2.0 Configuration/CLDC-1.1;320*240;CTC/2.0) IE/6.0 (compatible; MSIE 4.01; Windows CE; PPC)/UC Browser7.7.1.88",
+ "expect" :
+ {
+ "name" : "Windows Mobile",
+ "version" : "6.1"
+ }
+ },
+ {
+ "desc" : "Windows Mobile",
+ "ua" : "Opera/9.80 (Windows Mobile; WCE; Opera Mobi/WMD-50433; U; en) Presto/2.4.13 Version/10.00",
+ "expect" :
+ {
+ "name" : "Windows Mobile",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Windows Mobile",
+ "ua" : "Opera/9.7 (Windows Mobile; PPC; Opera Mobi/35166; U; en) Presto/2.2.1",
+ "expect" :
+ {
+ "name" : "Windows Mobile",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Windows Mobile 6.1",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11) 320x240; VZW; Motorola-Q9c; Windows Mobile 6.1 Standard",
+ "expect" :
+ {
+ "name" : "Windows Mobile",
+ "version" : "6.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/windows-phone.json b/test/data/ua/os/windows-phone.json
new file mode 100644
index 000000000..bf4bd0a26
--- /dev/null
+++ b/test/data/ua/os/windows-phone.json
@@ -0,0 +1,56 @@
+[
+ {
+ "desc" : "Windows Phone",
+ "ua" : "Opera/9.80 (Windows Phone; Opera Mini/7.6.8/35.7518; U; ru) Presto/2.8.119 Version/11.10",
+ "expect" :
+ {
+ "name" : "Windows Phone",
+ "version" : "undefined"
+ }
+ },
+ {
+ "desc" : "Windows Phone 6.5",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; HTC_Touch2_T3333; Windows Phone 6.5)",
+ "expect" :
+ {
+ "name" : "Windows Phone",
+ "version" : "6.5"
+ }
+ },
+ {
+ "desc" : "Windows Phone 7.0",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; DELL; Venue Pro)",
+ "expect" :
+ {
+ "name" : "Windows Phone",
+ "version" : "7.0"
+ }
+ },
+ {
+ "desc" : "Windows Phone 7.5",
+ "ua" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 800)",
+ "expect" :
+ {
+ "name" : "Windows Phone",
+ "version" : "7.5"
+ }
+ },
+ {
+ "desc" : "Windows Phone 8",
+ "ua" : "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; HTC; Windows Phone 8X by HTC)",
+ "expect" :
+ {
+ "name" : "Windows Phone",
+ "version" : "8.0"
+ }
+ },
+ {
+ "desc" : "Windows Phone 8.1",
+ "ua" : "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537",
+ "expect" :
+ {
+ "name" : "Windows Phone",
+ "version" : "8.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/windows-rt.json b/test/data/ua/os/windows-rt.json
new file mode 100644
index 000000000..8bee15174
--- /dev/null
+++ b/test/data/ua/os/windows-rt.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Windows RT 8",
+ "ua" : "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0)",
+ "expect" :
+ {
+ "name" : "Windows RT",
+ "version" : "8"
+ }
+ },
+ {
+ "desc" : "Windows RT 8.1",
+ "ua" : "Mozilla/5.0 (Windows NT 6.3; ARM; Trident/7.0; Touch; rv:11.0) like Gecko",
+ "expect" :
+ {
+ "name" : "Windows RT",
+ "version" : "8.1"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/windows.json b/test/data/ua/os/windows.json
new file mode 100644
index 000000000..faa965289
--- /dev/null
+++ b/test/data/ua/os/windows.json
@@ -0,0 +1,389 @@
+[
+ {
+ "desc" : "Windows 3.1",
+ "ua" : "NCSA_Mosaic/2.0 (Windows 3.1)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "3.1"
+ }
+ },
+ {
+ "desc" : "Windows 3.1",
+ "ua" : "Mozilla/1.22 (compatible; MSIE 2.0; Windows 3.1)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "3.1"
+ }
+ },
+ {
+ "desc" : "Windows NT",
+ "ua" : "Mozilla/4.51 [de] (WinNT; I)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "NT"
+ }
+ },
+ {
+ "desc" : "Windows NT 3.51",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "NT"
+ }
+ },
+ {
+ "desc" : "Windows NT 3.51",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 5.05; Windows NT 3.51)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "NT 3.51"
+ }
+ },
+ {
+ "desc" : "Windows NT 4.0",
+ "ua" : "Opera/8.41.(Windows NT 4.0; ts-ZA) Presto/2.9.178 Version/11.00",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "NT 4.0"
+ }
+ },
+ {
+ "desc" : "Windows NT 4.0",
+ "ua" : "Mozilla/5.0 (Windows; U; WinNT4.0; de-DE; rv:1.7.5) Gecko/20041108 Firefox/52.7.4",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "NT 4.0"
+ }
+ },
+ {
+ "desc" : "Netscape on Windows 95",
+ "ua" : "Mozilla/5.0 (Windows; U; Win95; de-DE; rv:0.9.2) Gecko/20010726 Netscape6/6.1",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "95"
+ }
+ },
+ {
+ "desc" : "Windows 95",
+ "ua" : "Mozilla/3.0 (Win95)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "95"
+ }
+ },
+ {
+ "desc" : "Windows 95",
+ "ua" : "Mozilla/3.0 (compatible; Opera/3.0; Windows 95/NT4) 3.2",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "95"
+ }
+ },
+ {
+ "desc" : "Windows 95",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) Opera 6.02 [en]",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "95"
+ }
+ },
+ {
+ "desc" : "Windows 95",
+ "ua" : "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "95"
+ }
+ },
+ {
+ "desc" : "Windows 98",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "98"
+ }
+ },
+ {
+ "desc" : "Firebird on Windows 98",
+ "ua" : "Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.5) Gecko/20031007 Firebird/0.7",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "98"
+ }
+ },
+ {
+ "desc" : "K-Meleon on Windows 98",
+ "ua" : "Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.5) Gecko/20031016 K-Meleon/0.8.2",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "98"
+ }
+ },
+ {
+ "desc" : "Windows ME",
+ "ua" : "Mozilla/5.0 (Windows; U; Win 9x 4.90) Gecko/20020502 CS 2000 7.0/7.0",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "ME"
+ }
+ },
+ {
+ "desc" : "Opera on Windows ME",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 5.0; Windows ME) Opera 5.12 [de]",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "ME"
+ }
+ },
+ {
+ "desc" : "Netscape on Windows ME",
+ "ua" : "Mozilla/5.0 (Windows; U; Win 9x 4.90; en-US; rv:1.8.1.8pre) Gecko/20071015 Firefox/2.0.0.7 Navigator/9.0",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "ME"
+ }
+ },
+ {
+ "desc" : "Netscape on Windows 2000",
+ "ua" : "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7.5) Gecko/20050519 Netscape/8.0.1",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "2000"
+ }
+ },
+ {
+ "desc" : "Opera on Windows 2000",
+ "ua" : "Opera/6.05 (Windows 2000; U)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "2000"
+ }
+ },
+ {
+ "desc" : "Opera on Windows 2000",
+ "ua" : "Opera/9.69 (Windows NT 5.01; en-US) Presto/2.8.160 Version/10.00",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "2000"
+ }
+ },
+ {
+ "desc" : "Windows 2000",
+ "ua" : "Mozilla/3.0 (compatible; MSIE 3.0; Windows NT 5.0)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "2000"
+ }
+ },
+ {
+ "desc" : "Windows XP",
+ "ua" : "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 5.2)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "XP"
+ }
+ },
+ {
+ "desc" : "Windows XP",
+ "ua" : "Mozilla/5.0 (Windows XP; U) Opera 6.05 [de]",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "XP"
+ }
+ },
+ {
+ "desc" : "Windows Vista",
+ "ua" : "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; fr-FR)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "Vista"
+ }
+ },
+ {
+ "desc" : "Windows 7",
+ "ua" : "Microsoft Windows 7",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "Windows 7",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Evernote Windows/306387 (pt-PT, DDL); Windows/6.1.0 (Win32); Safari/537.36",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "Windows 7",
+ "ua" : "Mozilla/5.0 (Windows 7 Enterprise; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6099.71 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "Windows 7",
+ "ua" : "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "Windows 8",
+ "ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C)",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "8"
+ }
+ },
+ {
+ "desc" : "Windows 8.1",
+ "ua" : "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:66.0.5) Gecko/20100101 Firefox/66.0.5",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "8.1"
+ }
+ },
+ {
+ "desc" : "Windows 10",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "10"
+ }
+ },
+ {
+ "desc" : "Windows Server 2012 R2",
+ "ua" : "Mozilla/5.0 (Windows Server 2012 R2 Standard; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.5975.80 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "Server 2012 R2"
+ }
+ },
+ {
+ "desc" : "WeChat Desktop for Windows Built-in Browser",
+ "ua" : "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI WindowsWechat QBCore/3.43.901.400 QQBrowser/9.0.2524.400",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "WeChat Desktop for Windows Built-in Browser major version in 4",
+ "ua" : "mozilla/5.0 (windows nt 6.1; wow64) applewebkit/537.36 (khtml, like gecko) chrome/81.0.4044.138 safari/537.36 nettype/wifi micromessenger/7.0.20.1781(0x6700143b) windowswechat",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows Vista",
+ "ua" : "iTunes/10.7 (Windows; Microsoft Windows Vista Home Premium Edition Service Pack 1 (Build 6001)) AppleWebKit/536.26.9",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "Vista"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 7",
+ "ua" : "iTunes/10.6.3 (Windows; Microsoft Windows 7 x64 Business Edition Service Pack 1 (Build 7601)) AppleWebKit/534.57.2",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "7"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 8",
+ "ua" : "iTunes/12.1.1 (Windows; Microsoft Windows 8 x64 Business Edition (Build 9200)) AppleWebKit/7600.1017.9000.2",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "8"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 8.1",
+ "ua" : "iTunes/12.4 (Windows; Microsoft Windows 8.1 x64 Business Edition (Build 9200); x64) AppleWebKit/7601.6016.1000.1",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "8.1"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 10",
+ "ua" : "iTunes/12.9.1 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 18362); x64) AppleWebKit/7606.2104.0.21",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "10"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 10",
+ "ua" : "iTunes/12.6.3 (Windows; Microsoft Windows 10.0 x64 (Build 17763); x64) AppleWebKit/7604.1038.1006.6",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "10"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 10 S",
+ "ua" : "iTunes/12.12 (Windows; Microsoft Windows 10 S x64; x64) AppleWebKit/7613.2007",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "10"
+ }
+ },
+ {
+ "desc" : "iTunes on Windows 11",
+ "ua" : "iTunes/12.13 (Windows; Microsoft Windows 11 x64; x64) AppleWebKit/7613.2007",
+ "expect" :
+ {
+ "name" : "Windows",
+ "version" : "11"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/xbox.json b/test/data/ua/os/xbox.json
new file mode 100644
index 000000000..f8f9f6784
--- /dev/null
+++ b/test/data/ua/os/xbox.json
@@ -0,0 +1,47 @@
+[
+ {
+ "desc" : "Xbox 360",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox 360) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Xbox",
+ "version" : "360"
+ }
+ },
+ {
+ "desc" : "Xbox One",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041",
+ "expect" :
+ {
+ "name" : "Xbox",
+ "version" : "One"
+ }
+ },
+ {
+ "desc" : "Xbox X",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02",
+ "expect" :
+ {
+ "name" : "Xbox",
+ "version" : "X"
+ }
+ },
+ {
+ "desc" : "Xbox Series X",
+ "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02 ",
+ "expect" :
+ {
+ "name" : "Xbox",
+ "version" : "Series X"
+ }
+ },
+ {
+ "desc" : "Xbox Series S",
+ "ua" : "Mozilla/5.0 (Compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Xbox; Xbox Series S)",
+ "expect" :
+ {
+ "name" : "Xbox",
+ "version" : "Series S"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/xubuntu.json b/test/data/ua/os/xubuntu.json
new file mode 100644
index 000000000..1f38a96ca
--- /dev/null
+++ b/test/data/ua/os/xubuntu.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Xubuntu",
+ "ua" : "Mozilla/5.0 (X11; Xubuntu Linux 21.10 x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.57 Vivaldi/5.1.2 (Blink)",
+ "expect" :
+ {
+ "name" : "Xubuntu",
+ "version" : "21.10"
+ }
+ },
+ {
+ "desc" : "Xubuntu",
+ "ua" : "Mozilla/5.0 (X11; Xubuntu 14.04.5 LTS) AppleWebKit/537.36 (KHTML, like Gecko) Xubuntu Chrome/66.0.2623.87 Safari/537.36",
+ "expect" :
+ {
+ "name" : "Xubuntu",
+ "version" : "14.04.5"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/data/ua/os/zenwalk.json b/test/data/ua/os/zenwalk.json
new file mode 100644
index 000000000..170db50ea
--- /dev/null
+++ b/test/data/ua/os/zenwalk.json
@@ -0,0 +1,20 @@
+[
+ {
+ "desc" : "Zenwalk",
+ "ua" : "Flock/2.16 (Zenwalk 7.3; es_PR;)",
+ "expect" :
+ {
+ "name" : "Zenwalk",
+ "version" : "7.3"
+ }
+ },
+ {
+ "desc" : "Zenwalk",
+ "ua" : "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101221 IceCat/3.6.13 (like Firefox/3.6.13) (Zenwalk GNU Linux)",
+ "expect" :
+ {
+ "name" : "Zenwalk",
+ "version" : "undefined"
+ }
+ }
+]
\ No newline at end of file
diff --git a/test/e2e/browser.spec.mjs b/test/e2e/browser.spec.mjs
new file mode 100644
index 000000000..848694c5c
--- /dev/null
+++ b/test/e2e/browser.spec.mjs
@@ -0,0 +1,154 @@
+import { test, expect } from '@playwright/test';
+import path from 'path';
+import url from 'url';
+
+const localHtml = `file://${path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '../../')}/dist/ua-parser.html`;
+const browserMap = {
+ chromium: 'Chrome Headless',
+ firefox: 'Firefox',
+ webkit: 'Safari'
+}
+
+test.describe('Custom navigator.userAgent tests', () => {
+
+ test('Undefined navigator.userAgent regarded as an empty user-agent string', async ({ page }) => {
+ await page.addInitScript(() => {
+ Object.defineProperty(navigator, 'userAgent', {
+ value: undefined
+ });
+ });
+ await page.goto(localHtml);
+ // @ts-ignore
+ const uap = await page.evaluate(async () => await UAParser());
+ expect(uap).toHaveProperty('ua', '');
+ expect(uap).toHaveProperty('browser.name', undefined);
+ });
+});
+
+test.describe('User-defined user-agent tests', () => {
+
+ test.beforeEach(async ({ page }) => {
+ await page.goto(localHtml);
+ });
+
+ test('Accept empty user-agent', async ({ page }) => {
+ // @ts-ignore
+ const uap = await page.evaluate(async () => await UAParser(''));
+ expect(uap).toHaveProperty('ua', '');
+ });
+
+ test('Truncate very long user-agent', async ({ page }) => {
+ // @ts-ignore
+ const uap = await page.evaluate(async () => await UAParser('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; (R1 1.6); SLCC1; .NET CLR 2.0.50727; InfoPath.2; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; .NET CLR 3.5.30729; .NET CLR 3.0.30618; 66760635803; runtime 11.00294; 876906799603; 97880703; 669602703; 9778063903; 877905603; 89670803; 96690803; 8878091903; 7879040603; 999608065603; 799808803; 6666059903; 669602102803; 888809342903; 696901603; 788907703; 887806555703; 97690214703; 66760903; 968909903; 796802422703; 8868026703; 889803611803; 898706903; 977806408603; 976900799903; 9897086903; 88780803; 798802301603; 9966008603; 66760703; 97890452603; 9789064803; 96990759803; 99960107703; 8868087903; 889801155603; 78890703; 8898070603; 89970603; 89970539603; 89970488703; 8789007603; 87890903; 877904603; 9887077703; 798804903; 97890264603; 967901703; 87890703; 97690420803; 79980706603; 9867086703; 996602846703; 87690803; 6989010903; 977809603; 666601903; 876905337803; 89670603; 89970200903; 786903603; 696901911703; 788905703; 896709803; 96890703; 998601903; 88980703; 666604769703; 978806603; 7988020803; 996608803; 788903297903; 98770043603; 899708803; 66960371603; 9669088903; 69990703; 99660519903; 97780603; 888801803; 9867071703; 79780803; 9779087603; 899708603; 66960456803; 898706824603; 78890299903; 99660703; 9768079803; 977901591603; 89670605603; 787903608603; 998607934903; 799808573903; 878909603; 979808146703; 9996088603; 797803154903; 69790603; 99660565603; 7869028603; 896707703; 97980965603; 976907191703; 88680703; 888809803; 69690903; 889805523703; 899707703; 997605035603; 89970029803; 9699094903; 877906803; 899707002703; 786905857603; 69890803; 97980051903; 997603978803; 9897097903; 66960141703; 7968077603; 977804603; 88980603; 989700803; 999607887803; 78690772803; 96990560903; 98970961603; 9996032903; 9699098703; 69890655603; 978903803; 698905066803; 977806903; 9789061703; 967903747703; 976900550903; 88980934703; 8878075803; 8977028703; 97980903; 9769006603; 786900803; 98770682703; 78790903; 878906967903; 87690399603; 99860976703; 796805703; 87990603; 968906803; 967904724603; 999606603; 988705903; 989702842603; 96790603; 99760703; 88980166703; 9799038903; 98670903; 697905248603; 7968043603; 66860703; 66860127903; 9779048903; 89670123903; 78890397703; 97890603; 87890803; 8789030603; 69990603; 88880763703; 9769000603; 96990203903; 978900405903; 7869022803; 699905422903; 97890703; 87990903; 878908703; 7998093903; 898702507603; 97780637603; 966907903; 896702603; 9769004803; 7869007903; 99660158803; 7899099603; 8977055803; 99660603; 7889080903; 66660981603; 997604603; 6969089803; 899701903; 9769072703; 666603903; 99860803; 997608803; 69790903; 88680756703; 979805677903; 9986047703; 89970803; 66660603; 96690903; 8997051603; 789901209803; 8977098903; 968900326803; 87790703; 98770024803; 697901794603; 69990803; 887805925803; 968908903; 97880603; 897709148703; 877909476903; 66760197703; 977908603; 698902703; 988706504803; 977802026603; 88680964703; 8878068703; 987705107903; 978902878703; 8898069803; 9768031703; 79680803; 79980803; 669609328703; 89870238703; 99960593903; 969904218703; 78890603; 9788000703; 69690630903; 889800982903; 988709748803; 7968052803; 99960007803; 969900800803; 668604817603; 66960903; 78790734603; 8868007703; 79780034903; 8878085903; 976907603; 89670830803; 877900903; 969904889703; 7978033903; 8987043903; 99860703; 979805903; 667603803; 976805348603; 999604127603; 97790701603; 78990342903; 98770672903; 87990253903; 9877027703; 97790803; 877901895603; 8789076903; 896708595603; 997601903; 799806903; 97690603; 87790371703; 667605603; 99760303703; 97680283803; 788902750803; 787909803; 79780603; 79880866903; 9986050903; 87890543903; 979800803; 97690179703; 876901603; 699909903; 96990192603; 878904903; 877904734903; 796801446903; 977904803; 9887044803; 797805565603; 98870789703; 7869093903; 87790727703; 797801232803; 666604803; 9778071903; 9799086703; 6969000903; 89670903; 8799075903; 897708903; 88680903; 97980362603; 97980503903; 889803256703; 88980388703; 789909376803; 69690703; 6969025903; 89970309903; 96690703; 877901847803; 968901903; 96690603; 88680607603; 7889001703; 789904761803; 976807703; 976902903; 878907889703; 9897014903; 896707046603; 696909903; 666603998903; 969902703; 79680421803; 9769075603; 798800192703; 97990903; 9689024903; 668604803; 969908671903; 9996094703; 69990642703; 97890895903; 977805619903; 79980859903; 88980443803; 98970649603; 997602703; 888802169903; 699907803; 667602028803; 786903283903; 997607703; 969909803; 798809925903; 9976045603; 97790903; 9789001903; 966903603; 9789069603; 968906603; 6989091803; 896701603; 6979059803; 978803903; 997606362603; 88980803; 98970803; 88880921703; 8997065703; 899700703; 698908703; 797801027903; 7889050903; 87890603; 78690703; 99660069703; 97980309903; 976800603; 666606803; 898707703; 79880019803; 66960250803; 7978049803; 88780602603; 79680903; 88880792703; 96990903; 667608603; 87790730903; 98970903; 9699032903; 8987004803; 88880703; 89770046603; 978800803; 969908903; 9798022603; 696901903; 799803703; 989703703; 668605903; 79780903; 998601371703; 796803339703; 87890922603; 898708903; 9966061903; 66960891903; 96790903; 8779050803; 98870858803; 976909298603; 9887029903; 669608703; 979806903; 878903803; 99960703; 9789086703; 979801803; 66960008703; 979806830803; 99760212703; 786906603; 797807603; 789907297703; 96990703; 786901603; 796807766603; 896702651603; 789902585603; 66660925903; 9986085703; 66960302703; 69890703; 789900703; 89970903; 9679060703; 9789002903; 979908821603; 986708140803; 976809828703; 7988082803; 79680997903; 99960803; 9788081903; 979805703; 787908603; 66960602803; 9887098703; 978803237703; 888806804603; 999604703; 977904703; 966904635703; 97680291703; 977809345603; 8878046703; 988709803; 976900773603; 989703903; 88780198603; 87790603; 986708703; 78890604703; 87790544803; 976809850903; 887806703; 987707527603; 79880803; 9897059603; 897709820603; 97880804803; 66960026703; 9789062803; 9867090803; 669600603; 8967087703; 78890903; 89770903; 97980703; 976802687603; 66860400803; 979901288603; 96990160903; 99860228903; 966900703; 66760603; 9689035703; 9779064703; 7968023603; 87890791903; 98770870603; 9798005803; 6969087903; 9779097903; 6979065703; 699903252603; 79780989703; 87690901803; 978905763903; 977809703; 97790369703; 899703269603; 8878012703; 78790803; 87690395603; 8888042803; 667607689903; 8977041803; 6666085603; 6999080703; 69990797803; 88680721603; 99660519803; 889807603; 87890146703; 699906325903; 89770603; 669608615903; 9779028803; 88880603; 97790703; 79780703; 97680355603; 6696024803; 78790784703; 97880329903; 9699077703; 89870803; 79680227903; 976905852703; 8997098903; 896704796703; 66860598803; 9897036703; 66960703; 9699094703; 9699008703; 97780485903; 999603179903; 89770834803; 96790445603; 79680460903; 9867009603; 89870328703; 799801035803; 989702903; 66960758903; 66860150803; 6686088603; 9877092803; 96990603; 99860603; 987703663603; 98870903; 699903325603; 87790803; 97680703; 8868030703; 9799030803; 89870703; 97680803; 9669054803; 6979097603; 987708046603; 999608603; 878904803; 998607408903; 968903903; 696900703; 977907491703; 6686033803; 669601803; 99960290603; 887809169903; 979803703; 69890903; 699901447903; 8987064903; 799800603; 98770903; 8997068703; 967903603; 66760146803; 978805087903; 697908138603; 799801603; 88780964903; 989708339903; 8967048603; 88880981603; 789909703; 796806603; 977905977603; 989700603; 97780703; 9669062603; 88980714603; 897709545903; 988701916703; 667604694903; 786905664603; 877900803; 886805490903; 89970559903; 99960531803; 7998033903; 98770803; 78890418703; 669600872803; 996605216603; 78690962703; 667604903; 996600903; 999608903; 9699083803; 787901803; 97780707603; 787905312703; 977805803; 8977033703; 97890708703; 989705521903; 978800703; 698905703; 78890376903; 878907703; 999602903; 986705903; 668602719603; 979901803; 997606903; 66760393903; 987703603; 78790338903; 96890803; 97680596803; 666601603; 977902178803; 877902803; 78790038603; 8868075703; 99960060603)'));
+
+ expect(uap).toHaveProperty('ua', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; (R1 1.6); SLCC1; .NET CLR 2.0.50727; InfoPath.2; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; .NET CLR 3.5.30729; .NET CLR 3.0.30618; 66760635803; runtime 11.00294; 876906799603; 97880703; 669602703; 9778063903; 877905603; 89670803; 96690803; 8878091903; 7879040603; 999608065603; 799808803; 6666059903; 669602102803; 888809342903; 696901603; 788907703; 887806555703; 97690214703; 66760903; 968909903; 796802422703; 8868026703; 8898036');
+ });
+});
+
+test.describe('withClientHints() tests', () => {
+
+ test('Detect custom Client Hints data', async ({ page, browserName }) => {
+ await page.addInitScript((browserName) => {
+ if (browserName == 'chromium') {
+ Object.defineProperty(navigator, 'userAgentData', {
+ value: {
+ brands: [],
+ platform: '',
+ mobile: false,
+ getHighEntropyValues: () => {
+ return Promise.resolve({
+ brands: [
+ {
+ brand: 'Chromium',
+ version: '110'
+ },
+ {
+ brand: 'Not(A:Brand',
+ version: '110'
+ },
+ {
+ brand: 'New Browser',
+ version: '110'
+ }
+ ],
+ platform: 'New OS',
+ formFactors: 'New Form Factor'
+ });
+ }
+ }
+ });
+ }
+ }, browserName);
+ await page.goto(localHtml);
+ // @ts-ignore
+ const uap = await page.evaluate(async () => await UAParser().withClientHints());
+ if (browserName == 'chromium') {
+ expect(uap).toHaveProperty('browser.name', 'New Browser');
+ expect(uap).toHaveProperty('os.name', 'New OS');
+ expect(uap).toHaveProperty('device.type', undefined);
+ } else {
+ expect(uap).toHaveProperty('browser.name', browserMap[browserName]);
+ }
+ });
+});
+
+test.describe('withFeatureCheck() tests', () => {
+
+ test('Detect Brave', async ({ page, browserName }) => {
+ await page.addInitScript(() => {
+ Object.defineProperty(navigator, 'brave', {
+ value: {
+ isBrave: () => true
+ }
+ });
+ });
+ await page.goto(localHtml);
+ // @ts-ignore
+ let uap = await page.evaluate(() => UAParser());
+ expect(uap).toHaveProperty('browser.name', browserMap[browserName]);
+ // @ts-ignore
+ uap = await page.evaluate(() => UAParser().withFeatureCheck());
+ expect(uap).toHaveProperty('browser.name', 'Brave');
+ });
+
+
+ test('Detect iPad', async ({ page }) => {
+ await page.addInitScript(() => {
+ 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
+ });
+ });
+ await page.goto(localHtml);
+ // @ts-ignore
+ let uap = await page.evaluate(() => UAParser());
+ expect(uap).toHaveProperty('device.model', 'Macintosh');
+ expect(uap).toHaveProperty('device.type', undefined);
+ // @ts-ignore
+ uap = await page.evaluate(() => UAParser().withFeatureCheck());
+ expect(uap).toHaveProperty('device.model', 'iPad');
+ expect(uap).toHaveProperty('device.type', 'tablet');
+ });
+});
+
+test.describe('request.headers can be passed in form of a Headers object', () => {
+
+ test('Headers automatically converted into a plain key-value object', async ({ page }) => {
+ await page.addInitScript(() => {
+ Object.defineProperty(window, 'req', {
+ value : {
+ headers: new Headers([["User-Agent", "myBrowser/1.0"]])
+ }
+ });
+ });
+ await page.goto(localHtml);
+ // @ts-ignore
+ const uap = await page.evaluate(() => UAParser(req.headers));
+ expect(uap.ua).toBe('myBrowser/1.0');
+ });
+});
diff --git a/test/fuzz/redos.js b/test/fuzz/redos.js
new file mode 100644
index 000000000..b04686218
--- /dev/null
+++ b/test/fuzz/redos.js
@@ -0,0 +1,20 @@
+const { FuzzedDataProvider } = require('@jazzer.js/core');
+const { UAParser } = require('../../dist/main');
+const UA_MAX_LENGTH = 500;
+
+module.exports.fuzz = function (buffer) {
+ const data = new FuzzedDataProvider(buffer);
+ const userAgent = data.consumeString(UA_MAX_LENGTH, 'utf-8', true);
+ const start = process.hrtime();
+
+ UAParser(userAgent);
+
+ const elapsed = process.hrtime(start);
+ const milisec = (elapsed[0]*1e3+elapsed[1]*1e-6).toFixed(3);
+ if (milisec > 1000) {
+ throw new Error(
+ `Potential ReDoS\n` +
+ `Time taken: ${milisec} ms.\n` +
+ `User agent: ${userAgent}`);
+ }
+};
\ No newline at end of file
diff --git a/test/os-test.json b/test/os-test.json
deleted file mode 100644
index 57a4d8a95..000000000
--- a/test/os-test.json
+++ /dev/null
@@ -1,1127 +0,0 @@
-[
- {
- "desc" : "Windows 95",
- "ua" : "Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "95"
- }
- },
- {
- "desc" : "Windows 98",
- "ua" : "Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "98"
- }
- },
- {
- "desc" : "Windows ME",
- "ua" : "Mozilla/5.0 (Windows; U; Win 9x 4.90) Gecko/20020502 CS 2000 7.0/7.0",
- "expect" :
- {
- "name" : "Windows",
- "version" : "ME"
- }
- },
- {
- "desc" : "Windows 2000",
- "ua" : "Mozilla/3.0 (compatible; MSIE 3.0; Windows NT 5.0)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "2000"
- }
- },
- {
- "desc" : "Windows XP",
- "ua" : "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 5.2)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "XP"
- }
- },
- {
- "desc" : "Windows Vista",
- "ua" : "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; fr-FR)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "Vista"
- }
- },
- {
- "desc" : "Windows 7",
- "ua" : "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "7"
- }
- },
- {
- "desc" : "Windows 8",
- "ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Win64; x64; Trident/6.0; .NET4.0E; .NET4.0C)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "8"
- }
- },
- {
- "desc" : "Windows 10",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0",
- "expect" :
- {
- "name" : "Windows",
- "version" : "10"
- }
- },
- {
- "desc" : "Windows RT",
- "ua" : "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "RT"
- }
- },
- {
- "desc" : "Windows CE",
- "ua" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11)",
- "expect" :
- {
- "name" : "Windows",
- "version" : "CE"
- }
- },
- {
- "desc" : "Windows Mobile",
- "ua" : "Mozilla/5.0 (ZTE-E_N72/N72V1.0.0B02;U;Windows Mobile/6.1;Profile/MIDP-2.0 Configuration/CLDC-1.1;320*240;CTC/2.0) IE/6.0 (compatible; MSIE 4.01; Windows CE; PPC)/UC Browser7.7.1.88",
- "expect" :
- {
- "name" : "Windows Mobile",
- "version" : "6.1"
- }
- },
- {
- "desc" : "Windows Mobile",
- "ua" : "Opera/9.80 (Windows Mobile; WCE; Opera Mobi/WMD-50433; U; en) Presto/2.4.13 Version/10.00",
- "expect" :
- {
- "name" : "Windows Mobile",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Windows Phone",
- "ua" : "Opera/9.80 (Windows Phone; Opera Mini/7.6.8/35.7518; U; ru) Presto/2.8.119 Version/11.10",
- "expect" :
- {
- "name" : "Windows Phone",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Windows Phone OS",
- "ua" : "Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; DELL; Venue Pro)",
- "expect" :
- {
- "name" : "Windows Phone OS",
- "version" : "7.0"
- }
- },
- {
- "desc" : "Windows Phone 8",
- "ua" : "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; HTC; Windows Phone 8X by HTC)",
- "expect" :
- {
- "name" : "Windows Phone",
- "version" : "8.0"
- }
- },
- {
- "desc" : "Windows NT on x86 or aarch64 CPU using Firefox",
- "ua" : "Mozilla/5.0 (Windows NT x.y; rv:10.0) Gecko/20100101 Firefox/10.0",
- "expect" :
- {
- "name" : "Windows",
- "version" : "NT x"
- }
- },
- {
- "desc" : "Windows NT on x64 CPU using Firefox",
- "ua" : "Mozilla/5.0 (Windows NT x.y; Win64; x64; rv:10.0) Gecko/20100101 Firefox/10.0",
- "expect" :
- {
- "name" : "Windows",
- "version" : "NT x"
- }
- },
- {
- "desc" : "BlackBerry",
- "ua" : "BlackBerry9300/5.0.0.912 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/378",
- "expect" :
- {
- "name" : "BlackBerry",
- "version" : "5.0.0.912"
- }
- },
- {
- "desc" : "BlackBerry 10",
- "ua" : "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.3+ (KHTML, like Gecko) Version/10.0.9.386 Mobile Safari/537.3+",
- "expect" :
- {
- "name" : "BlackBerry",
- "version" : "10"
- }
- },
- {
- "desc" : "Tizen",
- "ua" : "Mozilla/5.0 (SMART-TV; Linux; Tizen 2.3) AppleWebkit/538.1 (KHTML, like Gecko) SamsungBrowser/1.0 TV Safari/538.1",
- "expect" :
- {
- "name" : "Tizen",
- "version" : "2.3"
- }
- },
- {
- "desc" : "Tizen",
- "ua" : "Mozilla/5.0 (Linux; Tizen 2.3; SAMSUNG SM-Z130H) AppleWebKit/537.3 (KHTML, like Gecko) Version/2.3 Mobile Safari/537.3",
- "expect" :
- {
- "name" : "Tizen",
- "version" : "2.3"
- }
- },
- {
- "desc" : "Android",
- "ua" : "Mozilla/5.0 (Linux; U; Android 2.2.2; en-us; VM670 Build/FRG83G) AppleWebKit/533.1 (KHTML, like Gecko)",
- "expect" :
- {
- "name" : "Android",
- "version" : "2.2.2"
- }
- },
- {
- "desc" : "HarmonyOS",
- "ua" : "Mozilla/5.0 (Linux; Android 10; HarmonyOS; YAL-AL10; HMSCore 6.3.0.327; GMSCore 21.48.15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/12.0.3.310 Mobile Safari/537.36",
- "expect" :
- {
- "name" : "HarmonyOS",
- "version" : "10"
- }
- },
- {
- "desc" : "Sailfish",
- "ua" : "Mozilla/5.0 (Linux; U; Sailfish 3.0; Mobile; rv:45.0) Gecko/45.0 Firefox/45.0 SailfishBrowser/1.0",
- "expect" :
- {
- "name" : "Sailfish",
- "version" : "3.0"
- }
- },
- {
- "desc" : "WebOS",
- "ua" : "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.5; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.83 Safari/534.6 TouchPad/1.0",
- "expect" :
- {
- "name" : "webOS",
- "version" : "3.0.5"
- }
- },
- {
- "desc" : "WebOS",
- "ua" : "Mozilla/5.0 (webOS/1.4.5; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0",
- "expect" :
- {
- "name" : "webOS",
- "version" : "1.4.5"
- }
- },
- {
- "desc" : "WebOS TV 5.x",
- "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager",
- "expect" :
- {
- "name" : "webOS",
- "version" : "TV"
- }
- },
- {
- "desc" : "WebOS TV 4.x",
- "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager",
- "expect" :
- {
- "name" : "webOS",
- "version" : "TV"
- }
- },
- {
- "desc" : "WebOS TV 3.x",
- "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.2.1 Chrome/38.0.2125.122 Safari/537.36 WebAppManager",
- "expect" :
- {
- "name" : "webOS",
- "version" : "TV"
- }
- },
- {
- "desc" : "WebOS TV 2.x",
- "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/538.2 (KHTML, like Gecko) Large Screen WebAppManager Safari/538.2",
- "expect" :
- {
- "name" : "webOS",
- "version" : "TV"
- }
- },
- {
- "desc" : "WebOS TV 1.x",
- "ua" : "Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen WebAppManager Safari/537.41",
- "expect" :
- {
- "name" : "webOS",
- "version" : "TV"
- }
- },
- {
- "desc" : "QNX",
- "ua" : "Mozilla/5.0 (Photon; U; QNX x86pc; en-US; rv:1.8.1.20) Gecko/20090127 BonEcho/2.0.0.20",
- "expect" :
- {
- "name" : "QNX",
- "version" : "x86pc"
- }
- },
- {
- "desc" : "Bada",
- "ua" : "Mozilla/5.0 (SAMSUNG; SAMSUNG-GT-S5253/S5253DDKC1; U; Bada/1.0; en-us) AppleWebKit/533.1 (KHTML, like Gecko) Dolfin/2.0 Mobile WQVGA SMM-MMS/1.2.0 OPN-B",
- "expect" :
- {
- "name" : "Bada",
- "version" : "1.0"
- }
- },
- {
- "desc" : "RIM Tablet OS",
- "ua" : "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+",
- "expect" :
- {
- "name" : "RIM Tablet OS",
- "version" : "2.1.0"
- }
- },
- {
- "desc" : "Nokia N900 Linux mobile, on the Fennec browser",
- "ua" : "Mozilla/5.0 (Maemo; Linux armv7l; rv:10.0) Gecko/20100101 Firefox/10.0 Fennec/10.0",
- "expect" :
- {
- "name" : "Maemo",
- "version" : "undefined"
- }
- },
- {
- "desc" : "MeeGo",
- "ua" : "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13",
- "expect" :
- {
- "name" : "MeeGo",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Symbian",
- "ua" : "Nokia5250/10.0.011 (SymbianOS/9.4; U; Series60/5.0 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/525 (KHTML, like Gecko) Safari/525 3gpp-gba",
- "expect" :
- {
- "name" : "Symbian",
- "version" : "9.4"
- }
- },
- {
- "desc" : "Symbian",
- "ua" : "Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaC7-00/024.001; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.1.37 Mobile Safari/533.4 3gpp-gba",
- "expect" :
- {
- "name" : "Symbian",
- "version" : "5.2"
- }
- },
- {
- "desc" : "Series40",
- "ua" : "Mozilla/5.0 (Series40; Nokia2055/03.20; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/2.2.0.0.34",
- "expect" :
- {
- "name" : "Series40",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Firefox OS",
- "ua" : "Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0",
- "expect" :
- {
- "name" : "Firefox OS",
- "version" : "14.0"
- }
- },
- {
- "desc" : "Firefox OS on Tablet",
- "ua" : "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0",
- "expect" :
- {
- "name" : "Firefox OS",
- "version" : "26.0"
- }
- },
- {
- "desc" : "Firefox OS on TV",
- "ua" : "Mozilla/5.0 (TV; rv:44.0) Gecko/44.0 Firefox/44.0",
- "expect" :
- {
- "name" : "Firefox OS",
- "version" : "44.0"
- }
- },
- {
- "desc" : "Google Chromecast",
- "ua" : "Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.81 Safari/537.36 CrKey/1.42.183786",
- "expect" :
- {
- "name" : "Chromecast",
- "version" : "1.42.183786"
- }
- },
- {
- "desc" : "Nintendo Switch",
- "ua" : "Mozilla/5.0 (Nintendo Switch; WifiWebAuthApplet) AppleWebKit/606.4 (KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20393",
- "expect" :
- {
- "name" : "Nintendo",
- "version" : "Switch"
- }
- },
- {
- "desc" : "PlayStation 4",
- "ua" : "Mozilla/5.0 (PlayStation 4 3.00) AppleWebKit/537.73 (KHTML, like Gecko)",
- "expect" :
- {
- "name" : "PlayStation",
- "version" : "4"
- }
- },
- {
- "desc" : "Xbox 360",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox 360) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
- "expect" :
- {
- "name" : "Xbox",
- "version" : "360"
- }
- },
- {
- "desc" : "Xbox One",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One; WebView/3.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041",
- "expect" :
- {
- "name" : "Xbox",
- "version" : "One"
- }
- },
- {
- "desc" : "Xbox X",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02",
- "expect" :
- {
- "name" : "Xbox",
- "version" : "X"
- }
- },
- {
- "desc" : "Xbox Series X",
- "ua" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox Series X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36 Edge/20.02 ",
- "expect" :
- {
- "name" : "Xbox",
- "version" : "Series X"
- }
- },
- {
- "desc" : "Mint",
- "ua" : "Opera/9.80 (X11; Linux x86_64; Edition Linux Mint) Presto/2.12.388 Version/12.16",
- "expect" :
- {
- "name" : "Mint",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Mint",
- "ua" : "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1",
- "expect" :
- {
- "name" : "Mint",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Mint",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008121622 Linux Mint/6 (Felicia) Firefox/3.0.4",
- "expect" :
- {
- "name" : "Mint",
- "version" : "6"
- }
- },
- {
- "desc" : "Ubuntu",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.22+ (KHTML, like Gecko) Chromium/17.0.963.56 Chrome/17.0.963.56 Safari/535.22+ Ubuntu/12.04 (3.4.1-0ubuntu1) Epiphany/3.4.1",
- "expect" :
- {
- "name" : "Ubuntu",
- "version" : "12.04"
- }
- },
- {
- "desc" : "Ubuntu",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/31.0.1650.63 Chrome/31.0.1650.63 Safari/537.36",
- "expect" :
- {
- "name" : "Ubuntu",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Kubuntu",
- "ua" : "Mozilla/5.0 (compatible; Konqueror/4.4; Linux 2.6.32-22-generic; X11; en_US) KHTML/4.4.3 (like Gecko) Kubuntu",
- "expect" :
- {
- "name" : "Kubuntu",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Debian",
- "ua" : "Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.7 (like Gecko) (Debian)",
- "expect" :
- {
- "name" : "Debian",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Debian",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64; Debian GNU/Linux 8.1 (jessie)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.0 Maxthon/1.0.5.3 Safari/537.36",
- "expect" :
- {
- "name" : "Debian",
- "version" : "8.1"
- }
- },
- {
- "desc" : "Debian",
- "ua" : "ELinks/0.12~pre5-4 (textmode; Debian; Linux 3.2.0-4-amd64 x86_64 192x47-2)",
- "expect" :
- {
- "name" : "Debian",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Debian",
- "ua" : "w3m/0.5.3+debian-19",
- "expect" :
- {
- "name" : "debian",
- "version" : "19"
- }
- },
- {
- "desc" : "Debian",
- "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.3) Gecko/2008092814 (Debian-3.0.1-1)",
- "expect" :
- {
- "name" : "Debian",
- "version" : "3.0.1-1"
- }
- },
- {
- "desc" : "Debian",
- "ua" : "Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.24.4; X11) KHTML/3.5.9 (like Gecko) (Debian package 4:3.5.9.dfsg.1-2+b1)",
- "expect" :
- {
- "name" : "Debian",
- "version" : "undefined"
- }
- },
- {
- "desc" : "OpenSUSE",
- "ua" : "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.17) Gecko/20110420 SUSE/3.6.17-0.2.1 Firefox/3.6.17",
- "expect" :
- {
- "name" : "SUSE",
- "version" : "3.6.17-0.2.1"
- }
- },
- {
- "desc" : "Gentoo",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.16) Gecko/20080716 (Gentoo) Galeon/2.0.6",
- "expect" :
- {
- "name" : "Gentoo",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Gentoo",
- "ua" : "Xombrero (X11; U; Gentoo Linux amd64; en-US) Webkit/2.8.5",
- "expect" :
- {
- "name" : "Gentoo",
- "version" : "amd64"
- }
- },
- {
- "desc" : "Gentoo",
- "ua" : "Xombrero/1.6.4 (Linux amd64; en; Gentoo)",
- "expect" :
- {
- "name" : "Gentoo",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Gentoo",
- "ua" : "Links (2.8; Linux 3.17.2-gentoo-x86 i686; GNU C 4.8.2; x)",
- "expect" :
- {
- "name" : "gentoo",
- "version" : "x86"
- }
- },
- {
- "desc" : "Arch",
- "ua" : "Uzbl (Webkit 1.1.10) (Arch Linux)",
- "expect" :
- {
- "name" : "Arch",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Slackware",
- "ua" : "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41",
- "expect" :
- {
- "name" : "Slackware",
- "version" : "13.37"
- }
- },
- {
- "desc" : "Fedora",
- "ua" : "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0",
- "expect" :
- {
- "name" : "Fedora",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Fedora",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0",
- "expect" :
- {
- "name" : "Fedora",
- "version" : "16-dev"
- }
- },
- {
- "desc" : "Fedora",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; sk; rv:1.9.0.4) Gecko/2008111217 Fedora/3.0.4-1.fc10 Firefox/3.0.4",
- "expect" :
- {
- "name" : "Fedora",
- "version" : "3.0.4-1.fc10"
- }
- },
- {
- "desc" : "Mandriva",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.22) Gecko/20110907 Mandriva Linux/1.9.2.22-0.1mdv2010.2 (2010.2) Firefox/3.6.22",
- "expect" :
- {
- "name" : "Mandriva",
- "version" : "1.9.2.22-0.1mdv2010.2"
- }
- },
- {
- "desc" : "Chromium OS",
- "ua" : "Mozilla/5.0 (X11; CrOS x86_64 10575.58.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
- "expect" :
- {
- "name" : "Chromium OS",
- "version" : "10575.58.0"
- }
- },
- {
- "desc" : "Fuchsia",
- "ua" : "Mozilla/5.0 (X11; Fuchsia x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3557.0 Safari/537.36",
- "expect" :
- {
- "name" : "Fuchsia",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Solaris",
- "ua" : "Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:1.7) Gecko/20070606",
- "expect" :
- {
- "name" : "Solaris",
- "version" : "sun4u"
- }
- },
- {
- "desc" : "FreeBSD",
- "ua" : "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16",
- "expect" :
- {
- "name" : "FreeBSD",
- "version" : "undefined"
- }
- },
- {
- "desc" : "OpenBSD",
- "ua" : "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.1) Gecko/20090702 Firefox/3.5",
- "expect" :
- {
- "name" : "OpenBSD",
- "version" : "undefined"
- }
- },
- {
- "desc" : "NetBSD",
- "ua" : "ELinks (0.4.3; NetBSD 3.0.2PATCH sparc64; 141x19)",
- "expect" :
- {
- "name" : "NetBSD",
- "version" : "3.0.2PATCH"
- }
- },
- {
- "desc" : "DragonFly",
- "ua" : "Mozilla/5.0 (X11; U; DragonFly i386; de; rv:1.9.1) Gecko/20090720 Firefox/3.5.1",
- "expect" :
- {
- "name" : "DragonFly",
- "version" : "undefined"
- }
- },
- {
- "desc" : "iOS in App",
- "ua" : "AppName/version CFNetwork/version Darwin/version",
- "expect" :
- {
- "name" : "iOS",
- "version" : "undefined"
- }
- },
- {
- "desc" : "iOS with Chrome",
- "ua" : "Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3",
- "expect" :
- {
- "name" : "iOS",
- "version" : "5.1.1"
- }
- },
- {
- "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",
- "expect" :
- {
- "name" : "iOS",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Mac OS on PowerPC",
- "ua" : "Mozilla/4.0 (compatible; MSIE 5.0b1; Mac_PowerPC)",
- "expect" :
- {
- "name" : "Mac OS",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Mac OS X on x86, x86_64, or aarch64 using Firefox",
- "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:10.0) Gecko/20100101 Firefox/10.0",
- "expect" :
- {
- "name" : "Mac OS",
- "version" : "x.y"
- }
- },
- {
- "desc" : "Mac OS X on PowerPC using Firefox",
- "ua" : "Mozilla/5.0 (Macintosh; PPC Mac OS X x.y; rv:10.0) Gecko/20100101 Firefox/10.0",
- "expect" :
- {
- "name" : "Mac OS",
- "version" : "x.y"
- }
- },
- {
- "desc" : "Mac OS",
- "ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36",
- "expect" :
- {
- "name" : "Mac OS",
- "version" : "10.6.8"
- }
- },
- {
- "desc" : "Haiku",
- "ua" : "Mozilla/5.0 (Macintosh; Intel Haiku R1 x86) AppleWebKit/602.1.1 (KHTML, like Gecko) WebPositive/1.2 Version/8.0 Safari/602.1.1",
- "expect" :
- {
- "name" : "Haiku",
- "version" : "R1"
- }
- },
- {
- "desc" : "KaiOS",
- "ua" : "Mozilla/5.0 (Mobile; Nokia_8110_4G; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5",
- "expect" :
- {
- "name" : "KAIOS",
- "version" : "2.5"
- }
- },
- {
- "desc" : "iTunes Windows Vista",
- "ua" : "iTunes/10.7 (Windows; Microsoft Windows Vista Home Premium Edition Service Pack 1 (Build 6001)) AppleWebKit/536.26.9",
- "expect" :
- {
- "name" : "Windows",
- "version" : "Vista"
- }
- },
- {
- "desc" : "iOS BE App",
- "ua" : "APP-BE Test/1.0 (iPad; Apple; CPU iPhone OS 7_0_2 like Mac OS X)",
- "expect" :
- {
- "name" : "iOS",
- "version" : "7.0.2"
- }
- },
- {
- "desc" : "KTB-Nexus 5",
- "ua" : "APP-My App/1.0 (Linux; Android 4.2.1; Nexus 5 Build/JOP40D)",
- "expect" :
- {
- "name" : "Android",
- "version" : "4.2.1"
- }
- },
- {
- "desc" : "Solaris",
- "ua" : "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)",
- "expect" :
- {
- "name" : "Solaris",
- "version" : "4.1.4"
- }
- },
- {
- "desc" : "Raspbian",
- "ua" : "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/72.0.3626.121 HeadlessChrome/72.0.3626.121 Safari/537.36",
- "expect" :
- {
- "name" : "Raspbian",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Raspbian",
- "ua" : "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/538.15 (KHTML, like Gecko) Version/8.0 Safari/538.15 Raspbian/9.0 (1:3.8.2.0-0rpi28) Epiphany/3.8.2",
- "expect" :
- {
- "name" : "Raspbian",
- "version" : "9.0"
- }
- },
- {
- "desc" : "AIX",
- "ua" : "Mozilla/5.0 (X11; U; AIX 000138384C00; en-US; rv:1.0.1) Gecko/20030213 Netscape/7.0",
- "expect" :
- {
- "name" : "AIX",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Plan9",
- "ua" : "NCSA_Mosaic/5.0 (X11;Plan 9 4.0)",
- "expect" :
- {
- "name" : "Plan 9",
- "version" : "4.0"
- }
- },
- {
- "desc" : "Minix",
- "ua" : "Mozilla/5.0 (X11; Original ; Minix 3.3 ; rv:3.0)",
- "expect" :
- {
- "name" : "Minix",
- "version" : "3.3"
- }
- },
- {
- "desc" : "BeOS",
- "ua" : "Mozilla/5.0 (BeOS; U; BeOS BePC; en-US; rv:1.8.1.8pre) Gecko/20070926 SeaMonkey/1.1.5pre",
- "expect" :
- {
- "name" : "BeOS",
- "version" : "undefined"
- }
- },
- {
- "desc" : "OS/2",
- "ua" : "Links (2.1pre14; OS/2 1 i386; 80x33)",
- "expect" :
- {
- "name" : "OS/2",
- "version" : "undefined"
- }
- },
- {
- "desc" : "AmigaOS",
- "ua" : "Mozilla/4.0 (compatible; AWEB 3.4 SE; AmigaOS)",
- "expect" :
- {
- "name" : "AmigaOS",
- "version" : "undefined"
- }
- },
- {
- "desc" : "MorphOS",
- "ua" : "AmigaVoyager/3.4.4 (MorphOS/PPC native)",
- "expect" :
- {
- "name" : "MorphOS",
- "version" : "undefined"
- }
- },
- {
- "desc" : "UNIX",
- "ua" : "Surf/0.4.1 (X11; U; Unix; en-US) AppleWebKit/531.2+ Compatible (Safari)",
- "expect" :
- {
- "name" : "Unix",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Joli",
- "ua" : "Mozilla/5.0 (X11; Jolicloud Linux i686) AppleWebKit/537.6 (KHTML, like Gecko) Joli OS/1.2 Chromium/23.0.1240.0 Chrome/23.0.1240.0 Safari/537.6",
- "expect" :
- {
- "name" : "Joli",
- "version" : "1.2"
- }
- },
- {
- "desc" : "CentOS",
- "ua" : "Konqueror/15.13 (CentOS Linux 7.4; cs-CZ;)",
- "expect" :
- {
- "name" : "CentOS",
- "version" : "7.4"
- }
- },
- {
- "desc" : "PCLinuxOS",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) Gecko/20101209 PCLinuxOS/1.9.2.13-1pclos2010 (2010) Firefox/3.6.13",
- "expect" :
- {
- "name" : "PCLinuxOS",
- "version" : "1.9.2.13-1pclos2010"
- }
- },
- {
- "desc" : "RedHat",
- "ua" : "Mozilla/5.0 (compatible; Konqueror/4.3; Linux) KHTML/4.3.4 (like Gecko) Red Hat Enterprise Linux/4.3.4-11.el6_1.4",
- "expect" :
- {
- "name" : "Red Hat",
- "version" : "4.3.4-11.el6_1.4"
- }
- },
- {
- "desc" : "RedHat",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.13pre) Gecko/20070717 Red Hat/1.0.9-4.el4 SeaMonkey/1.0.9",
- "expect" :
- {
- "name" : "Red Hat",
- "version" : "1.0.9-4.el4"
- }
- },
- {
- "desc" : "RedHat",
- "ua" : "iTunes/4.7.1 (Linux; N; Red Hat; x86_64-linux; EN; utf8) SqueezeCenter, Squeezebox Server, Logitech Media Server/7.9.1/1522157629",
- "expect" :
- {
- "name" : "Red Hat",
- "version" : "undefined"
- }
- },
- {
- "desc" : "RedHat",
- "ua" : "curl/7.20.0 (x86_64-redhat-linux-gnu) libcurl/7.20.0 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5",
- "expect" :
- {
- "name" : "redhat",
- "version" : "undefined"
- }
- },
- {
- "desc" : "RISC OS",
- "ua" : "Mozilla/1.10 [en] (Compatible; RISC OS 3.70; Oregano 1.10)",
- "expect" :
- {
- "name" : "RISC OS",
- "version" : "3.70"
- }
- },
- {
- "desc" : "Zenwalk",
- "ua" : "Flock/2.16 (Zenwalk 7.3; es_PR;)",
- "expect" :
- {
- "name" : "Zenwalk",
- "version" : "7.3"
- }
- },
- {
- "desc" : "Hurd",
- "ua" : "Mozilla/5.0 (X11; Hurd 0.9 i386; en-US) libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.7.0 Safari/696.96",
- "expect" :
- {
- "name" : "Hurd",
- "version" : "0.9"
- }
- },
- {
- "desc" : "Linux",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
- "expect" :
- {
- "name" : "Linux",
- "version" : "x86_64"
- }
- },
- {
- "desc" : "Deepin",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64; Deepin 15.5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36 NFSBrowser/5.0.0.1886",
- "expect" :
- {
- "name" : "Deepin",
- "version" : "15.5"
- }
- },
- {
- "desc" : "Palm OS",
- "ua" : "Mozilla/4.76 [en] (PalmOS; U; WebPro3.0; Palm-Arz1)",
- "expect" :
- {
- "name" : "Palm",
- "version" : "undefined"
- }
- },
- {
- "desc" : "HP-UX",
- "ua" : "Mozilla/5.0 (X11; U; HP-UX 9000/785; es-ES; rv:1.0.1) Gecko/20020827 Netscape/7.0",
- "expect" :
- {
- "name" : "HP-UX",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Contiki",
- "ua" : "Contiki/1.0 (Commodore 64; http://dunkels.com/adam/contiki/)",
- "expect" :
- {
- "name" : "Contiki",
- "version" : "1.0"
- }
- },
- {
- "desc" : "Linpus",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9b5pre) Gecko/2008032619 Linpus/3.0-0.49",
- "expect" :
- {
- "name" : "Linpus",
- "version" : "3.0-0.49"
- }
- },
- {
- "desc" : "Manjaro",
- "ua" : "Mozilla/5.0 (X11; Manjaro 19.0.2; Arch; x64; rv:84.0) Gecko/20100101 Firefox/84.0",
- "expect" :
- {
- "name" : "Manjaro",
- "version" : "19.0.2"
- }
- },
- {
- "desc" : "elementary OS",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/604.1 (KHTML, like Gecko) Version/11.0 Safari/604.1 elementary OS/0.4 (Loki) Epiphany/3.18.11",
- "expect" :
- {
- "name" : "elementary OS",
- "version" : "0.4"
- }
- },
- {
- "desc" : "GhostBSD",
- "ua" : "Mozilla/5.0 (X11; GhostBSD/10.3; x86_64; rv:50.0.1) Gecko/20100101 Firefox/50.0.1",
- "expect" :
- {
- "name" : "GhostBSD",
- "version" : "10.3"
- }
- },
- {
- "desc" : "Android-x86",
- "ua" : "Mozilla/5.0 (Linux; Android 7.1.2; Generic Android-x86) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 OPR/61.2.3076.56749",
- "expect" :
- {
- "name" : "Android-x86",
- "version" : "7.1.2"
- }
- },
- {
- "desc" : "Sabayon",
- "ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Sabayon Chrome/19.0.1084.46 Safari/536.5",
- "expect" :
- {
- "name" : "Sabayon",
- "version" : "undefined"
- }
- },
- {
- "desc" : "Linspire",
- "ua" : "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.4) Gecko/20060803 Firefox/1.5.0.4 Linspire/1.5.0.4",
- "expect" :
- {
- "name" : "Linspire",
- "version" : "1.5.0.4"
- }
- }
-]
diff --git a/test/static/dts-lint.ts b/test/static/dts-lint.ts
new file mode 100644
index 000000000..bcfc26287
--- /dev/null
+++ b/test/static/dts-lint.ts
@@ -0,0 +1,49 @@
+import { expectType } from 'tsd';
+import { UAParser, IResult, IBrowser, ICPU, IEngine, IDevice, IOS } from "../../src/main/ua-parser";
+import { isAppleSilicon, isChromeFamily } from "../../src/helpers/ua-parser-helpers";
+
+const uastring = 'Mozilla/5.0 (X11; MyCustomOS; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.0';
+const extensions = {
+ os : [
+ [/(mycustomos)/], [UAParser.OS.NAME, [UAParser.OS.VERSION, '10']]
+ ]
+};
+const headers = {
+ 'sec-ch-ua-mobile' : '?1'
+};
+
+expectType(UAParser());
+expectType(UAParser(uastring));
+expectType(UAParser(uastring, extensions));
+expectType(UAParser(uastring, headers));
+expectType(UAParser(extensions, headers));
+expectType(UAParser(extensions));
+expectType(UAParser(headers));
+expectType(new UAParser());
+
+const parser = new UAParser(uastring);
+const browser = parser.getBrowser();
+
+expectType(browser);
+expectType(browser.name);
+expectType(browser.version);
+expectType(browser.major);
+expectType<'crawler' | 'cli' | 'email' | 'fetcher' | 'inapp' | 'mediaplayer' | 'library' | undefined>(browser.type);
+expectType(browser.is(''));
+expectType(browser.toString());
+expectType>(browser.withClientHints());
+expectType>((browser.withClientHints()).withFeatureCheck());
+expectType(((browser.withClientHints()).withFeatureCheck()).is(''));
+
+expectType(parser.getCPU());
+expectType(parser.getDevice());
+expectType(parser.getEngine());
+expectType(parser.getOS());
+expectType(parser.getResult());
+expectType(parser.getUA());
+expectType(parser.setUA(uastring));
+
+const result = parser.getResult();
+
+expectType(isAppleSilicon(result));
+expectType(isChromeFamily(result));
\ No newline at end of file
diff --git a/test/test.js b/test/test.js
deleted file mode 100644
index e1fd175d2..000000000
--- a/test/test.js
+++ /dev/null
@@ -1,168 +0,0 @@
-var fs = require('fs');
-var safe = require('safe-regex');
-var assert = require('assert');
-var requirejs = require('requirejs');
-var parseJS = require('@babel/parser').parse;
-var traverse = require('@babel/traverse').default;
-var UAParser = require('./../src/ua-parser');
-var browsers = require('./browser-test.json');
-var cpus = require('./cpu-test.json');
-var devices = require('./device-test.json');
-var engines = require('./engine-test.json');
-var os = require('./os-test.json');
-var parser = new UAParser();
-var methods = [
- {
- title : 'getBrowser',
- label : 'browser',
- list : browsers,
- properties : ['name', 'major', 'version']
- },
- {
- title : 'getCPU',
- label : 'cpu',
- list : cpus,
- properties : ['architecture']
- },
- {
- title : 'getDevice',
- label : 'device',
- list : devices,
- properties : ['model', 'type', 'vendor']
- },
- {
- title : 'getEngine',
- label : 'engine',
- list : engines,
- properties : ['name', 'version']
- },
- {
- title : 'getOS',
- label : 'os',
- list : os,
- properties : ['name', 'version']
-}];
-
-describe('UAParser()', function () {
- var ua = 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6';
- assert.deepStrictEqual(UAParser(ua), new UAParser().setUA(ua).getResult());
-});
-
-describe('UAParser() constructor does not throw with undefined ua argument', function () {
- assert.doesNotThrow(() => new UAParser(undefined).getResult());
-});
-
-describe('UAParser.setUA method does not throw with undefined ua argument', function () {
- assert.doesNotThrow(() => new UAParser().setUA(undefined).getResult());
-});
-
-for (var i in methods) {
- describe(methods[i]['title'], function () {
- for (var j in methods[i]['list']) {
- if (!!methods[i]['list'][j].ua) {
- describe('[' + methods[i]['list'][j].desc + ']', function () {
- describe('"' + methods[i]['list'][j].ua + '"', function () {
- var expect = methods[i]['list'][j].expect;
- var result = parser.setUA(methods[i]['list'][j].ua).getResult()[methods[i]['label']];
-
- methods[i]['properties'].forEach(function(m) {
- it('should return ' + methods[i]['label'] + ' ' + m + ': ' + expect[m], function () {
- assert.strictEqual(result[m], expect[m] != 'undefined' ? expect[m] : undefined);
- });
- });
- });
- });
- }
- }
- });
-}
-
-describe('Returns', function () {
- it('getResult() should returns JSON', function(done) {
- assert.deepStrictEqual(new UAParser('').getResult(),
- {
- ua : '',
- browser: { name: undefined, version: undefined, major: undefined },
- cpu: { architecture: undefined },
- device: { vendor: undefined, model: undefined, type: undefined },
- engine: { name: undefined, version: undefined},
- os: { name: undefined, version: undefined }
- });
- done();
- });
-});
-
-describe('Extending Regex', function () {
- var uaString = 'Mozilla/5.0 MyOwnBrowser/1.3';
- var myOwnBrowser = [[/(myownbrowser)\/((\d+)?[\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION, UAParser.BROWSER.MAJOR]];
-
- var parser1 = new UAParser(uaString, {browser: myOwnBrowser});
- assert.strictEqual(parser1.getBrowser().name, 'MyOwnBrowser');
- assert.strictEqual(parser1.getBrowser().version, '1.3');
- assert.strictEqual(parser1.getBrowser().major, '1');
-
- var parser2 = new UAParser({browser: myOwnBrowser});
- assert.strictEqual(parser2.getBrowser().name, undefined);
- parser2.setUA(uaString);
- assert.strictEqual(parser2.getBrowser().name, 'MyOwnBrowser');
- assert.strictEqual(parser1.getBrowser().version, '1.3');
-});
-
-describe('User-agent length', function () {
- var UA_MAX_LENGTH = 350;
-
- // Real data from https://stackoverflow.com/questions/654921/how-big-can-a-user-agent-string-get#answer-6595973
- var uaString = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; (R1 1.6); SLCC1; .NET CLR 2.0.50727; InfoPath.2; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; .NET CLR 3.5.30729; .NET CLR 3.0.30618; 66760635803; runtime 11.00294; 876906799603; 97880703; 669602703; 9778063903; 877905603; 89670803; 96690803; 8878091903; 7879040603; 999608065603; 799808803; 6666059903; 669602102803; 888809342903; 696901603; 788907703; 887806555703; 97690214703; 66760903; 968909903; 796802422703; 8868026703; 889803611803; 898706903; 977806408603; 976900799903; 9897086903; 88780803; 798802301603; 9966008603; 66760703; 97890452603; 9789064803; 96990759803; 99960107703; 8868087903; 889801155603; 78890703; 8898070603; 89970603; 89970539603; 89970488703; 8789007603; 87890903; 877904603; 9887077703; 798804903; 97890264603; 967901703; 87890703; 97690420803; 79980706603; 9867086703; 996602846703; 87690803; 6989010903; 977809603; 666601903; 876905337803; 89670603; 89970200903; 786903603; 696901911703; 788905703; 896709803; 96890703; 998601903; 88980703; 666604769703; 978806603; 7988020803; 996608803; 788903297903; 98770043603; 899708803; 66960371603; 9669088903; 69990703; 99660519903; 97780603; 888801803; 9867071703; 79780803; 9779087603; 899708603; 66960456803; 898706824603; 78890299903; 99660703; 9768079803; 977901591603; 89670605603; 787903608603; 998607934903; 799808573903; 878909603; 979808146703; 9996088603; 797803154903; 69790603; 99660565603; 7869028603; 896707703; 97980965603; 976907191703; 88680703; 888809803; 69690903; 889805523703; 899707703; 997605035603; 89970029803; 9699094903; 877906803; 899707002703; 786905857603; 69890803; 97980051903; 997603978803; 9897097903; 66960141703; 7968077603; 977804603; 88980603; 989700803; 999607887803; 78690772803; 96990560903; 98970961603; 9996032903; 9699098703; 69890655603; 978903803; 698905066803; 977806903; 9789061703; 967903747703; 976900550903; 88980934703; 8878075803; 8977028703; 97980903; 9769006603; 786900803; 98770682703; 78790903; 878906967903; 87690399603; 99860976703; 796805703; 87990603; 968906803; 967904724603; 999606603; 988705903; 989702842603; 96790603; 99760703; 88980166703; 9799038903; 98670903; 697905248603; 7968043603; 66860703; 66860127903; 9779048903; 89670123903; 78890397703; 97890603; 87890803; 8789030603; 69990603; 88880763703; 9769000603; 96990203903; 978900405903; 7869022803; 699905422903; 97890703; 87990903; 878908703; 7998093903; 898702507603; 97780637603; 966907903; 896702603; 9769004803; 7869007903; 99660158803; 7899099603; 8977055803; 99660603; 7889080903; 66660981603; 997604603; 6969089803; 899701903; 9769072703; 666603903; 99860803; 997608803; 69790903; 88680756703; 979805677903; 9986047703; 89970803; 66660603; 96690903; 8997051603; 789901209803; 8977098903; 968900326803; 87790703; 98770024803; 697901794603; 69990803; 887805925803; 968908903; 97880603; 897709148703; 877909476903; 66760197703; 977908603; 698902703; 988706504803; 977802026603; 88680964703; 8878068703; 987705107903; 978902878703; 8898069803; 9768031703; 79680803; 79980803; 669609328703; 89870238703; 99960593903; 969904218703; 78890603; 9788000703; 69690630903; 889800982903; 988709748803; 7968052803; 99960007803; 969900800803; 668604817603; 66960903; 78790734603; 8868007703; 79780034903; 8878085903; 976907603; 89670830803; 877900903; 969904889703; 7978033903; 8987043903; 99860703; 979805903; 667603803; 976805348603; 999604127603; 97790701603; 78990342903; 98770672903; 87990253903; 9877027703; 97790803; 877901895603; 8789076903; 896708595603; 997601903; 799806903; 97690603; 87790371703; 667605603; 99760303703; 97680283803; 788902750803; 787909803; 79780603; 79880866903; 9986050903; 87890543903; 979800803; 97690179703; 876901603; 699909903; 96990192603; 878904903; 877904734903; 796801446903; 977904803; 9887044803; 797805565603; 98870789703; 7869093903; 87790727703; 797801232803; 666604803; 9778071903; 9799086703; 6969000903; 89670903; 8799075903; 897708903; 88680903; 97980362603; 97980503903; 889803256703; 88980388703; 789909376803; 69690703; 6969025903; 89970309903; 96690703; 877901847803; 968901903; 96690603; 88680607603; 7889001703; 789904761803; 976807703; 976902903; 878907889703; 9897014903; 896707046603; 696909903; 666603998903; 969902703; 79680421803; 9769075603; 798800192703; 97990903; 9689024903; 668604803; 969908671903; 9996094703; 69990642703; 97890895903; 977805619903; 79980859903; 88980443803; 98970649603; 997602703; 888802169903; 699907803; 667602028803; 786903283903; 997607703; 969909803; 798809925903; 9976045603; 97790903; 9789001903; 966903603; 9789069603; 968906603; 6989091803; 896701603; 6979059803; 978803903; 997606362603; 88980803; 98970803; 88880921703; 8997065703; 899700703; 698908703; 797801027903; 7889050903; 87890603; 78690703; 99660069703; 97980309903; 976800603; 666606803; 898707703; 79880019803; 66960250803; 7978049803; 88780602603; 79680903; 88880792703; 96990903; 667608603; 87790730903; 98970903; 9699032903; 8987004803; 88880703; 89770046603; 978800803; 969908903; 9798022603; 696901903; 799803703; 989703703; 668605903; 79780903; 998601371703; 796803339703; 87890922603; 898708903; 9966061903; 66960891903; 96790903; 8779050803; 98870858803; 976909298603; 9887029903; 669608703; 979806903; 878903803; 99960703; 9789086703; 979801803; 66960008703; 979806830803; 99760212703; 786906603; 797807603; 789907297703; 96990703; 786901603; 796807766603; 896702651603; 789902585603; 66660925903; 9986085703; 66960302703; 69890703; 789900703; 89970903; 9679060703; 9789002903; 979908821603; 986708140803; 976809828703; 7988082803; 79680997903; 99960803; 9788081903; 979805703; 787908603; 66960602803; 9887098703; 978803237703; 888806804603; 999604703; 977904703; 966904635703; 97680291703; 977809345603; 8878046703; 988709803; 976900773603; 989703903; 88780198603; 87790603; 986708703; 78890604703; 87790544803; 976809850903; 887806703; 987707527603; 79880803; 9897059603; 897709820603; 97880804803; 66960026703; 9789062803; 9867090803; 669600603; 8967087703; 78890903; 89770903; 97980703; 976802687603; 66860400803; 979901288603; 96990160903; 99860228903; 966900703; 66760603; 9689035703; 9779064703; 7968023603; 87890791903; 98770870603; 9798005803; 6969087903; 9779097903; 6979065703; 699903252603; 79780989703; 87690901803; 978905763903; 977809703; 97790369703; 899703269603; 8878012703; 78790803; 87690395603; 8888042803; 667607689903; 8977041803; 6666085603; 6999080703; 69990797803; 88680721603; 99660519803; 889807603; 87890146703; 699906325903; 89770603; 669608615903; 9779028803; 88880603; 97790703; 79780703; 97680355603; 6696024803; 78790784703; 97880329903; 9699077703; 89870803; 79680227903; 976905852703; 8997098903; 896704796703; 66860598803; 9897036703; 66960703; 9699094703; 9699008703; 97780485903; 999603179903; 89770834803; 96790445603; 79680460903; 9867009603; 89870328703; 799801035803; 989702903; 66960758903; 66860150803; 6686088603; 9877092803; 96990603; 99860603; 987703663603; 98870903; 699903325603; 87790803; 97680703; 8868030703; 9799030803; 89870703; 97680803; 9669054803; 6979097603; 987708046603; 999608603; 878904803; 998607408903; 968903903; 696900703; 977907491703; 6686033803; 669601803; 99960290603; 887809169903; 979803703; 69890903; 699901447903; 8987064903; 799800603; 98770903; 8997068703; 967903603; 66760146803; 978805087903; 697908138603; 799801603; 88780964903; 989708339903; 8967048603; 88880981603; 789909703; 796806603; 977905977603; 989700603; 97780703; 9669062603; 88980714603; 897709545903; 988701916703; 667604694903; 786905664603; 877900803; 886805490903; 89970559903; 99960531803; 7998033903; 98770803; 78890418703; 669600872803; 996605216603; 78690962703; 667604903; 996600903; 999608903; 9699083803; 787901803; 97780707603; 787905312703; 977805803; 8977033703; 97890708703; 989705521903; 978800703; 698905703; 78890376903; 878907703; 999602903; 986705903; 668602719603; 979901803; 997606903; 66760393903; 987703603; 78790338903; 96890803; 97680596803; 666601603; 977902178803; 877902803; 78790038603; 8868075703; 99960060603)';
-
- it('greater than ' + UA_MAX_LENGTH + ' should be trimmed down', function () {
- assert.strictEqual(UAParser(uaString).ua.length, UA_MAX_LENGTH);
- });
-});
-
-describe('Using Require.js', function () {
- it('should loaded automatically', function(done) {
- requirejs.config({
- baseUrl : 'dist',
- paths : {
- 'ua-parser-js' : 'ua-parser.min'
- }
- });
- requirejs(['ua-parser-js'], function(ua) {
- var parser = new ua('Dillo/1.0');
- assert.deepStrictEqual(parser.getBrowser().name, 'Dillo');
- done();
- });
- });
-});
-
-describe('Testing regexes', function () {
-
- var regexes;
-
- before('Read main js file', function () {
- var code = fs.readFileSync('src/ua-parser.js', 'utf8').toString();
- var ast = parseJS(code, { sourceType: "script" });
- regexes = [];
- traverse(ast, {
- RegExpLiteral: (path) => {
- regexes.push(path.node.pattern);
- }
- });
-
- if (regexes.length === 0) {
- throw new Error("Regexes cannot be empty!");
- }
- });
-
- describe('Begin testing', function () {
- it('all regexes in main file', function () {
- regexes.forEach(function (regex) {
- describe('Test against `safe-regex` : ' + regex, function () {
- it('should be safe from potentially vulnerable regex', function () {
- assert.strictEqual(safe(regex), true);
- });
- });
- });
- });
- });
-});
diff --git a/test/unit/es6.mjs b/test/unit/es6.mjs
new file mode 100644
index 000000000..8e26908d5
--- /dev/null
+++ b/test/unit/es6.mjs
@@ -0,0 +1,26 @@
+import { UAParser } from '../../src/main/ua-parser.mjs';
+import { CPU, Device, Engine } from '../../src/enums/ua-parser-enums.mjs';
+import * as assert from 'assert';
+
+describe('Returns', () => {
+ it('getResult() should returns object', () => {
+ assert.deepEqual(new UAParser('').getResult(),
+ {
+ ua : '',
+ browser: { name: undefined, version: undefined, major: undefined, type: undefined },
+ cpu: { architecture: undefined },
+ device: { vendor: undefined, model: undefined, type: undefined },
+ engine: { name: undefined, version: undefined},
+ os: { name: undefined, version: undefined }
+ });
+ });
+});
+
+describe('Enums', () => {
+ it('Can use enum', () => {
+ const { cpu, device, engine } = UAParser('Mozilla/5.0 (X11; U; Linux armv7l; en-GB; rv:1.9.2a1pre) Gecko/20090928 Firefox/3.5 Maemo Browser 1.4.1.22 RX-51 N900');
+ assert.strictEqual(cpu.is(CPU.ARM), true);
+ assert.strictEqual(device.is(Device.MOBILE), true);
+ assert.strictEqual(engine.is(Engine.GECKO), true);
+ });
+});
\ No newline at end of file
diff --git a/test/unit/extensions.js b/test/unit/extensions.js
new file mode 100644
index 000000000..9f1fa9630
--- /dev/null
+++ b/test/unit/extensions.js
@@ -0,0 +1,114 @@
+const fs = require('fs');
+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');
+
+describe('Extensions', () => {
+ [
+ ['CLIs', 'cli', CLIs],
+ ['Crawlers', 'crawler', Crawlers],
+ ['Emails', 'email', Emails],
+ ['Fetchers', 'fetcher', Fetchers],
+ ['InApps', 'inapp', InApps],
+ ['Libraries', 'library', Libraries],
+ ['Vehicles', 'vehicle', Vehicles]
+ ]
+ .forEach(([desc, path, ext]) => {
+ const tests = require(`../data/ua/extension/${path}.json`);
+ describe(desc, () => {
+ tests.forEach((test) => {
+ it(`Can detect ${test.desc}: "${test.ua}"`, () => {
+ const { browser, device } = UAParser(test.ua, ext);
+ if ('browser' in ext) {
+ assert.strictEqual(String(browser.name), test.expect.name);
+ assert.strictEqual(String(browser.version), test.expect.version);
+ assert.strictEqual(String(browser.type), test.expect.type);
+ } else if ('device' in ext) {
+ assert.strictEqual(String(device.vendor), test.expect.vendor);
+ assert.strictEqual(String(device.model), test.expect.model);
+ assert.strictEqual(String(device.type), test.expect.type);
+ }
+ });
+ });
+ });
+ });
+
+ 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)';
+
+ assert.equal(UAParser(scrapy, Bots).browser.name, 'Scrapy');
+
+ const emailParser = new UAParser(Emails);
+ assert.deepEqual(emailParser.setUA(outlook).getBrowser(), {name: "Microsoft Outlook", version: "16.0.9126", major: "16", type: "email"});
+ assert.deepEqual(emailParser.setUA(thunderbird).getBrowser(), {name: "Thunderbird", version: "78.13.0", major: "78", type: "email"});
+
+ const libraryParser = new UAParser(Libraries);
+ assert.deepEqual(libraryParser.setUA(axios).getBrowser(), {name: "axios", version: "1.3.5", major: "1", type: "library"});
+ assert.deepEqual(libraryParser.setUA(jsdom).getBrowser(), {name: "jsdom", version: "20.0.3", major: "20", type: "library"});
+ assert.deepEqual(libraryParser.setUA(scrapy).getBrowser(), {name: "Scrapy", version: "1.5.0", major: "1", type: "library"});
+
+ // Bluesky
+ const bluesky = 'Mozilla/5.0 (compatible; Bluesky Cardyb/1.1; +mailto:support@bsky.app)';
+ assert.deepEqual(new UAParser(bluesky, Bots).getBrowser(), {
+ name: 'Bluesky',
+ version: '1.1',
+ major: '1',
+ type: 'fetcher'
+ });
+
+ const whatsapp = "WhatsApp/2.0 A";
+ assert.deepEqual(new UAParser(whatsapp, Fetchers).getOS(), {
+ name : 'Android',
+ version : undefined
+ });
+});
+
+describe('Merge', () => {
+ it('Can merge multiple extensions', () => {
+ const wget = 'Wget/1.21.1';
+ const facebookBot = 'Mozilla/5.0 (compatible; FacebookBot/1.0; +https://developers.facebook.com/docs/sharing/webmasters/facebookbot/)';
+
+ // try merging crawlers & CLIs
+ const crawlersAndCLIs = { browser : [...Crawlers.browser, ...CLIs.browser]};
+ const crawlersAndCLIsParser = new UAParser(crawlersAndCLIs);
+ assert.deepEqual(crawlersAndCLIsParser.setUA(wget).getBrowser(), {name: "Wget", version: "1.21.1", major: "1", type:"cli"});
+ assert.deepEqual(crawlersAndCLIsParser.setUA(facebookBot).getBrowser(), {name: "FacebookBot", version: "1.0", major: "1", type:"crawler"});
+
+ // alternative merge options
+ const crawlersAndCLIsParser2 = new UAParser([Crawlers, CLIs]);
+ const crawlersAndCLIsParser3 = new UAParser(facebookBot, [Crawlers, CLIs]);
+ assert.deepEqual(crawlersAndCLIsParser2.setUA(wget).getBrowser(), {name: "Wget", version: "1.21.1", major: "1", type:"cli"});
+ assert.deepEqual(crawlersAndCLIsParser3.getBrowser(), {name: "FacebookBot", version: "1.0", major: "1", type:"crawler"});
+ });
+});
+
+describe('Testing the safety of regexes', () => {
+
+ let regexes;
+ let code = fs.readFileSync('src/extensions/ua-parser-extensions.js', 'utf8').toString();
+ let ast = parseJS(code, { sourceType: 'script' });
+ regexes = [];
+ traverse(ast, {
+ RegExpLiteral: (path) => {
+ regexes.push(path.node.pattern);
+ }
+ });
+
+ if (regexes.length === 0) {
+ throw new Error('Regexes cannot be empty!');
+ }
+
+ describe('Checking for potentially vulnerable regex', () => {
+ for (let regex of regexes) {
+ it('Test against `safe-regex` : ' + regex, () => {
+ assert.strictEqual(safe(regex), true);
+ });
+ }
+ });
+});
\ No newline at end of file
diff --git a/test/unit/helpers.js b/test/unit/helpers.js
new file mode 100644
index 000000000..127133bec
--- /dev/null
+++ b/test/unit/helpers.js
@@ -0,0 +1,84 @@
+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');
+
+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), 'Samsung');
+ assert.equal(getDeviceVendor(modelRedmi), 'Xiaomi');
+ assert.equal(getDeviceVendor(modelNexus), 'Huawei');
+ assert.equal(getDeviceVendor(modelAquos), '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);
+ });
+});
\ No newline at end of file
diff --git a/test/unit/main.js b/test/unit/main.js
new file mode 100644
index 000000000..f9b2ef961
--- /dev/null
+++ b/test/unit/main.js
@@ -0,0 +1,384 @@
+var fs = require('fs');
+var safe = require('safe-regex');
+var assert = require('assert');
+var requirejs = require('requirejs');
+var parseJS = require('@babel/parser').parse;
+var traverse = require('@babel/traverse').default;
+var {UAParser} = require('../../src/main/ua-parser');
+var browsers = require('../data/ua/browser/browser-all.json');
+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 = [];
+ fs.readdirSync(dir).forEach(function (file) {
+ list.push(...JSON.parse(fs.readFileSync(`${dir}/${file}`, 'utf-8')));
+ });
+ return list;
+};
+
+describe('UAParser()', function () {
+ var ua = 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6';
+ assert.deepEqual(UAParser(ua), new UAParser().setUA(ua).getResult());
+});
+
+describe('UAParser() constructor does not throw with undefined ua argument', function () {
+ assert.doesNotThrow(() => new UAParser(undefined).getResult());
+});
+
+describe('UAParser.setUA method does not throw with undefined ua argument', function () {
+ assert.doesNotThrow(() => new UAParser().setUA(undefined).getResult());
+});
+
+describe('UAParser get*() methods', () => {
+ [
+ {
+ title : 'getBrowser()',
+ label : 'browser',
+ list : browsers
+ },
+ {
+ title : 'getCPU()',
+ label : 'cpu',
+ list : cpus
+ },
+ {
+ title : 'getDevice()',
+ label : 'device',
+ list : devices
+ },
+ {
+ title : 'getEngine()',
+ label : 'engine',
+ list : engines
+ },
+ {
+ title : 'getOS()',
+ label : 'os',
+ list : os
+ }
+ ]
+ .forEach(method => {
+ describe(`[${method.title}]`, () => {
+ method.list.forEach(unit => {
+ describe(`[${unit.desc}]: "${unit.ua}"`, () => {
+ const actual = UAParser(unit.ua)[method.label];
+ Object.entries(unit.expect).forEach(entry => {
+ const [key, val] = entry;
+ it(`Should return ${key}: ${val}`, () => {
+ assert.strictEqual(String(val), String(actual[key]));
+ });
+ });
+ });
+ });
+ });
+ });
+});
+
+describe('Returns', function () {
+ it('getResult() should returns JSON', function(done) {
+ assert.deepEqual(new UAParser('').getResult(),
+ {
+ ua : '',
+ browser: { name: undefined, version: undefined, major: undefined, type: undefined },
+ cpu: { architecture: undefined },
+ device: { vendor: undefined, model: undefined, type: undefined },
+ engine: { name: undefined, version: undefined},
+ os: { name: undefined, version: undefined }
+ });
+ done();
+ });
+
+ it('works even when Array.prototype has been mangled', function(done) {
+ const result = withMangledArrayProto(() => new UAParser('').getResult());
+
+ function withMangledArrayProto(fn, key = 'isEmpty', value = function() { return this.length === 0; }) {
+ const originalValue = Array.prototype[key];
+ const restore = Object.hasOwnProperty.call(Array.prototype, key)
+ ? () => Array.prototype[key] = originalValue
+ : () => delete Array.prototype[key];
+
+ Array.prototype[key] = value;
+ const result = fn();
+ restore();
+
+ return result;
+ }
+
+ assert.deepEqual(result,
+ {
+ ua : '',
+ browser: { name: undefined, version: undefined, major: undefined, type: undefined },
+ cpu: { architecture: undefined },
+ device: { vendor: undefined, model: undefined, type: undefined },
+ engine: { name: undefined, version: undefined},
+ os: { name: undefined, version: undefined }
+ });
+ done();
+ });
+});
+
+describe('Extending Regex', function () {
+ var uaString = 'Mozilla/5.0 MyOwnBrowser/1.3';
+ var myOwnBrowser = [[/(myownbrowser)\/((\d+)?[\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION, UAParser.BROWSER.MAJOR]];
+
+ var parser1 = new UAParser(uaString, {browser: myOwnBrowser});
+ assert.strictEqual(parser1.getBrowser().name, 'MyOwnBrowser');
+ assert.strictEqual(parser1.getBrowser().version, '1.3');
+ assert.strictEqual(parser1.getBrowser().major, '1');
+
+ var parser2 = new UAParser({browser: myOwnBrowser});
+ assert.strictEqual(parser2.getBrowser().name, undefined);
+ parser2.setUA(uaString);
+ assert.strictEqual(parser2.getBrowser().name, 'MyOwnBrowser');
+ assert.strictEqual(parser1.getBrowser().version, '1.3');
+
+ let myOwnListOfBrowsers = [
+ [/(mybrowser)\/([\w\.]+)/i], [UAParser.BROWSER.NAME, UAParser.BROWSER.VERSION, ['type', 'bot']]
+ ];
+ let myParser = new UAParser({ browser: myOwnListOfBrowsers });
+ let myUA = 'Mozilla/5.0 MyBrowser/1.3';
+ assert.deepEqual(myParser.setUA(myUA).getBrowser(), {name: "MyBrowser", version: "1.3", major: "1", type : "bot"});
+ assert.strictEqual(myParser.getBrowser().is('bot'), true);
+
+ let myOwnListOfDevices = [
+ [/(mytab) ([\w ]+)/i], [UAParser.DEVICE.VENDOR, UAParser.DEVICE.MODEL, [UAParser.DEVICE.TYPE, UAParser.DEVICE.TABLET]],
+ [/(myphone)/i], [UAParser.DEVICE.VENDOR, [UAParser.DEVICE.TYPE, UAParser.DEVICE.MOBILE]]
+ ];
+ let myParser2 = new UAParser({
+ browser: myOwnListOfBrowsers,
+ device: myOwnListOfDevices
+ });
+ let myUA2 = 'Mozilla/5.0 MyTab 14 Pro Max';
+ assert.deepEqual(myParser2.setUA(myUA2).getDevice(), {vendor: "MyTab", model: "14 Pro Max", type: "tablet"});
+
+ let myParser3 = new UAParser([{
+ browser: myOwnListOfBrowsers
+ }, {
+ device: myOwnListOfDevices
+ }]);
+ assert.deepEqual(myParser3.setUA(myUA2).getDevice(), {vendor: "MyTab", model: "14 Pro Max", type: "tablet"});
+});
+
+describe('User-agent length', function () {
+ var UA_MAX_LENGTH = 500;
+
+ // Real data from https://stackoverflow.com/questions/654921/how-big-can-a-user-agent-string-get#answer-6595973
+ var uaString = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; (R1 1.6); SLCC1; .NET CLR 2.0.50727; InfoPath.2; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; .NET CLR 3.5.30729; .NET CLR 3.0.30618; 66760635803; runtime 11.00294; 876906799603; 97880703; 669602703; 9778063903; 877905603; 89670803; 96690803; 8878091903; 7879040603; 999608065603; 799808803; 6666059903; 669602102803; 888809342903; 696901603; 788907703; 887806555703; 97690214703; 66760903; 968909903; 796802422703; 8868026703; 889803611803; 898706903; 977806408603; 976900799903; 9897086903; 88780803; 798802301603; 9966008603; 66760703; 97890452603; 9789064803; 96990759803; 99960107703; 8868087903; 889801155603; 78890703; 8898070603; 89970603; 89970539603; 89970488703; 8789007603; 87890903; 877904603; 9887077703; 798804903; 97890264603; 967901703; 87890703; 97690420803; 79980706603; 9867086703; 996602846703; 87690803; 6989010903; 977809603; 666601903; 876905337803; 89670603; 89970200903; 786903603; 696901911703; 788905703; 896709803; 96890703; 998601903; 88980703; 666604769703; 978806603; 7988020803; 996608803; 788903297903; 98770043603; 899708803; 66960371603; 9669088903; 69990703; 99660519903; 97780603; 888801803; 9867071703; 79780803; 9779087603; 899708603; 66960456803; 898706824603; 78890299903; 99660703; 9768079803; 977901591603; 89670605603; 787903608603; 998607934903; 799808573903; 878909603; 979808146703; 9996088603; 797803154903; 69790603; 99660565603; 7869028603; 896707703; 97980965603; 976907191703; 88680703; 888809803; 69690903; 889805523703; 899707703; 997605035603; 89970029803; 9699094903; 877906803; 899707002703; 786905857603; 69890803; 97980051903; 997603978803; 9897097903; 66960141703; 7968077603; 977804603; 88980603; 989700803; 999607887803; 78690772803; 96990560903; 98970961603; 9996032903; 9699098703; 69890655603; 978903803; 698905066803; 977806903; 9789061703; 967903747703; 976900550903; 88980934703; 8878075803; 8977028703; 97980903; 9769006603; 786900803; 98770682703; 78790903; 878906967903; 87690399603; 99860976703; 796805703; 87990603; 968906803; 967904724603; 999606603; 988705903; 989702842603; 96790603; 99760703; 88980166703; 9799038903; 98670903; 697905248603; 7968043603; 66860703; 66860127903; 9779048903; 89670123903; 78890397703; 97890603; 87890803; 8789030603; 69990603; 88880763703; 9769000603; 96990203903; 978900405903; 7869022803; 699905422903; 97890703; 87990903; 878908703; 7998093903; 898702507603; 97780637603; 966907903; 896702603; 9769004803; 7869007903; 99660158803; 7899099603; 8977055803; 99660603; 7889080903; 66660981603; 997604603; 6969089803; 899701903; 9769072703; 666603903; 99860803; 997608803; 69790903; 88680756703; 979805677903; 9986047703; 89970803; 66660603; 96690903; 8997051603; 789901209803; 8977098903; 968900326803; 87790703; 98770024803; 697901794603; 69990803; 887805925803; 968908903; 97880603; 897709148703; 877909476903; 66760197703; 977908603; 698902703; 988706504803; 977802026603; 88680964703; 8878068703; 987705107903; 978902878703; 8898069803; 9768031703; 79680803; 79980803; 669609328703; 89870238703; 99960593903; 969904218703; 78890603; 9788000703; 69690630903; 889800982903; 988709748803; 7968052803; 99960007803; 969900800803; 668604817603; 66960903; 78790734603; 8868007703; 79780034903; 8878085903; 976907603; 89670830803; 877900903; 969904889703; 7978033903; 8987043903; 99860703; 979805903; 667603803; 976805348603; 999604127603; 97790701603; 78990342903; 98770672903; 87990253903; 9877027703; 97790803; 877901895603; 8789076903; 896708595603; 997601903; 799806903; 97690603; 87790371703; 667605603; 99760303703; 97680283803; 788902750803; 787909803; 79780603; 79880866903; 9986050903; 87890543903; 979800803; 97690179703; 876901603; 699909903; 96990192603; 878904903; 877904734903; 796801446903; 977904803; 9887044803; 797805565603; 98870789703; 7869093903; 87790727703; 797801232803; 666604803; 9778071903; 9799086703; 6969000903; 89670903; 8799075903; 897708903; 88680903; 97980362603; 97980503903; 889803256703; 88980388703; 789909376803; 69690703; 6969025903; 89970309903; 96690703; 877901847803; 968901903; 96690603; 88680607603; 7889001703; 789904761803; 976807703; 976902903; 878907889703; 9897014903; 896707046603; 696909903; 666603998903; 969902703; 79680421803; 9769075603; 798800192703; 97990903; 9689024903; 668604803; 969908671903; 9996094703; 69990642703; 97890895903; 977805619903; 79980859903; 88980443803; 98970649603; 997602703; 888802169903; 699907803; 667602028803; 786903283903; 997607703; 969909803; 798809925903; 9976045603; 97790903; 9789001903; 966903603; 9789069603; 968906603; 6989091803; 896701603; 6979059803; 978803903; 997606362603; 88980803; 98970803; 88880921703; 8997065703; 899700703; 698908703; 797801027903; 7889050903; 87890603; 78690703; 99660069703; 97980309903; 976800603; 666606803; 898707703; 79880019803; 66960250803; 7978049803; 88780602603; 79680903; 88880792703; 96990903; 667608603; 87790730903; 98970903; 9699032903; 8987004803; 88880703; 89770046603; 978800803; 969908903; 9798022603; 696901903; 799803703; 989703703; 668605903; 79780903; 998601371703; 796803339703; 87890922603; 898708903; 9966061903; 66960891903; 96790903; 8779050803; 98870858803; 976909298603; 9887029903; 669608703; 979806903; 878903803; 99960703; 9789086703; 979801803; 66960008703; 979806830803; 99760212703; 786906603; 797807603; 789907297703; 96990703; 786901603; 796807766603; 896702651603; 789902585603; 66660925903; 9986085703; 66960302703; 69890703; 789900703; 89970903; 9679060703; 9789002903; 979908821603; 986708140803; 976809828703; 7988082803; 79680997903; 99960803; 9788081903; 979805703; 787908603; 66960602803; 9887098703; 978803237703; 888806804603; 999604703; 977904703; 966904635703; 97680291703; 977809345603; 8878046703; 988709803; 976900773603; 989703903; 88780198603; 87790603; 986708703; 78890604703; 87790544803; 976809850903; 887806703; 987707527603; 79880803; 9897059603; 897709820603; 97880804803; 66960026703; 9789062803; 9867090803; 669600603; 8967087703; 78890903; 89770903; 97980703; 976802687603; 66860400803; 979901288603; 96990160903; 99860228903; 966900703; 66760603; 9689035703; 9779064703; 7968023603; 87890791903; 98770870603; 9798005803; 6969087903; 9779097903; 6979065703; 699903252603; 79780989703; 87690901803; 978905763903; 977809703; 97790369703; 899703269603; 8878012703; 78790803; 87690395603; 8888042803; 667607689903; 8977041803; 6666085603; 6999080703; 69990797803; 88680721603; 99660519803; 889807603; 87890146703; 699906325903; 89770603; 669608615903; 9779028803; 88880603; 97790703; 79780703; 97680355603; 6696024803; 78790784703; 97880329903; 9699077703; 89870803; 79680227903; 976905852703; 8997098903; 896704796703; 66860598803; 9897036703; 66960703; 9699094703; 9699008703; 97780485903; 999603179903; 89770834803; 96790445603; 79680460903; 9867009603; 89870328703; 799801035803; 989702903; 66960758903; 66860150803; 6686088603; 9877092803; 96990603; 99860603; 987703663603; 98870903; 699903325603; 87790803; 97680703; 8868030703; 9799030803; 89870703; 97680803; 9669054803; 6979097603; 987708046603; 999608603; 878904803; 998607408903; 968903903; 696900703; 977907491703; 6686033803; 669601803; 99960290603; 887809169903; 979803703; 69890903; 699901447903; 8987064903; 799800603; 98770903; 8997068703; 967903603; 66760146803; 978805087903; 697908138603; 799801603; 88780964903; 989708339903; 8967048603; 88880981603; 789909703; 796806603; 977905977603; 989700603; 97780703; 9669062603; 88980714603; 897709545903; 988701916703; 667604694903; 786905664603; 877900803; 886805490903; 89970559903; 99960531803; 7998033903; 98770803; 78890418703; 669600872803; 996605216603; 78690962703; 667604903; 996600903; 999608903; 9699083803; 787901803; 97780707603; 787905312703; 977805803; 8977033703; 97890708703; 989705521903; 978800703; 698905703; 78890376903; 878907703; 999602903; 986705903; 668602719603; 979901803; 997606903; 66760393903; 987703603; 78790338903; 96890803; 97680596803; 666601603; 977902178803; 877902803; 78790038603; 8868075703; 99960060603)';
+
+ it('greater than ' + UA_MAX_LENGTH + ' should be trimmed down', function () {
+ assert.strictEqual(UAParser(uaString).ua.length, UA_MAX_LENGTH);
+ });
+});
+
+describe('Using Require.js', function () {
+ it('should loaded automatically', function(done) {
+ requirejs.config({
+ baseUrl : 'dist',
+ paths : {
+ 'ua-parser-js' : 'ua-parser.min'
+ }
+ });
+ requirejs(['ua-parser-js'], function(ua) {
+ var parser = new ua('Dillo/1.0');
+ assert.deepStrictEqual(parser.getBrowser().name, 'Dillo');
+ done();
+ });
+ });
+});
+
+describe('Testing regexes', function () {
+
+ var regexes;
+
+ before('Read main js file', function () {
+ var code = fs.readFileSync('src/main/ua-parser.js', 'utf8').toString();
+ var ast = parseJS(code, { sourceType: "script" });
+ regexes = [];
+ traverse(ast, {
+ RegExpLiteral: (path) => {
+ regexes.push(path.node.pattern);
+ }
+ });
+
+ if (regexes.length === 0) {
+ throw new Error("Regexes cannot be empty!");
+ }
+ });
+
+ describe('Begin testing', function () {
+ it('all regexes in main file', function () {
+ describe('Test against `safe-regex` module', function () {
+ regexes.forEach(function (regex) {
+ it(`Should pass \`safe-regex\`: ${regex}`, function () {
+ assert.strictEqual(safe(regex), true);
+ });
+ });
+ });
+ });
+ });
+});
+
+
+describe('is() utility method', function () {
+ let uap = new UAParser('Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537');
+
+ it('Should match full name', function () {
+ assert.strictEqual(uap.getBrowser().name, "IEMobile");
+ assert.strictEqual(uap.getBrowser().is("IEMobile"), true);
+ assert.strictEqual(uap.getBrowser().is("IE"), false);
+ assert.strictEqual(uap.getBrowser().is("11.0"), false);
+ });
+
+ it('Should ignore "Browser" suffix', function () {
+ assert.strictEqual(uap.getBrowser().is("IEMobile Browser"), true);
+ });
+
+ it('Should ignore case', function () {
+ assert.strictEqual(uap.getEngine().name, "Trident");
+ assert.strictEqual(uap.getEngine().is("tRiDeNt"), true);
+ assert.strictEqual(uap.getEngine().is("7.0"), false);
+ });
+
+ it('Should get exact name', function () {
+ assert.strictEqual(uap.getOS().name, "Windows Phone");
+ assert.strictEqual(uap.getOS().is("Windows Phone"), true);
+ assert.strictEqual(uap.getOS().is("Windows Phone OS"), true);
+ assert.strictEqual(uap.getOS().is("Windows Mobile"), false);
+ assert.strictEqual(uap.getOS().is("Android"), false);
+ });
+
+ it('Should check all device properties', function () {
+ assert.deepEqual(uap.getDevice(), {
+ vendor : "Nokia",
+ model : "Lumia 635",
+ type : "mobile"
+ });
+ assert.strictEqual(uap.getDevice().is("Nokia"), true);
+ assert.strictEqual(uap.getDevice().is("Lumia 635"), true);
+ assert.strictEqual(uap.getDevice().is("mobile"), true);
+
+ assert.strictEqual(uap.getResult().device.is("Nokia"), true);
+ });
+
+ it('Should get result after reassignment', function () {
+ uap.setUA("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36");
+ assert.strictEqual(uap.getOS().name, "macOS");
+ assert.strictEqual(uap.getOS().is("Mac OS"), true);
+ assert.strictEqual(uap.getOS().is("macOS"), true);
+ assert.strictEqual(uap.getOS().is("mac OS"), true);
+
+ assert.strictEqual(uap.getOS().is("M ac"), false);
+ assert.strictEqual(uap.getOS().is("M a c "), false);
+ assert.strictEqual(uap.getOS().is("Mac OS OS"), false);
+ assert.strictEqual(uap.getOS().is("Mac OS X"), false);
+
+ assert.strictEqual(uap.getBrowser().is("Chrome"), true);
+ assert.strictEqual(uap.getEngine().is("Blink"), true);
+ });
+
+ it('Should refrain from "undefined" until all properties are checked', function () {
+ assert.strictEqual(uap.getDevice().is("undefined"), false);
+ assert.strictEqual(uap.getDevice().is("Apple"), true);
+
+ uap.setUA("");
+ assert.strictEqual(uap.getDevice().model, undefined);
+ assert.strictEqual(uap.getDevice().is("undefined"), false);
+ assert.strictEqual(uap.getDevice().is(undefined), true);
+ });
+
+ //it('Should accept arch equivalent name', function () {
+ it('Should accept exact arch name', function () {
+ uap.setUA("Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:19.0) Gecko/20100101 Firefox/19.0");
+ assert.strictEqual(uap.getCPU().architecture, "ia32");
+ assert.strictEqual(uap.getCPU().is("ia32"), true);
+ assert.strictEqual(uap.getCPU().is("x86"), false);
+
+ uap.setUA("Opera/9.80 (X11; Linux x86_64; U; Linux Mint; en) Presto/2.2.15 Version/10.10");
+ assert.strictEqual(uap.getCPU().architecture, "amd64");
+ assert.strictEqual(uap.getCPU().is("amd64"), true);
+ assert.strictEqual(uap.getCPU().is("x86-64"), false);
+ assert.strictEqual(uap.getCPU().is("x64"), false);
+ });
+});
+
+describe('toString() utility method', function () {
+ it('Should return full name', function () {
+ let uap = new UAParser('Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537');
+ assert.strictEqual(uap.getBrowser().name, "IEMobile");
+ assert.strictEqual(uap.getBrowser().version, "11.0");
+ assert.strictEqual(uap.getBrowser().major, "11");
+ assert.strictEqual(uap.getBrowser().toString(), "IEMobile 11.0");
+
+ assert.strictEqual(uap.getCPU().architecture, "arm");
+ assert.strictEqual(uap.getCPU().toString(), "arm");
+
+ assert.strictEqual(uap.getDevice().vendor, "Nokia");
+ assert.strictEqual(uap.getDevice().model, "Lumia 635");
+ assert.strictEqual(uap.getDevice().type, "mobile");
+ assert.strictEqual(uap.getDevice().toString(), "Nokia Lumia 635");
+
+ assert.strictEqual(uap.getEngine().name, "Trident");
+ assert.strictEqual(uap.getEngine().version, "7.0");
+ assert.strictEqual(uap.getEngine().toString(), "Trident 7.0");
+
+ assert.strictEqual(uap.getOS().name, "Windows Phone");
+ assert.strictEqual(uap.getOS().version, "8.1");
+ assert.strictEqual(uap.getOS().toString(), "Windows Phone 8.1");
+ });
+});
+
+describe('Read user-agent data from req.headers', function () {
+ const ua = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)';
+ const ext = {
+ engine : [
+ [/(msie)/i], [[UAParser.ENGINE.NAME, 'Custom Browser 1']],
+ [/(edge)/i], [[UAParser.ENGINE.NAME, 'Custom Browser 2']]
+ ]
+ };
+ const req = {
+ headers : {
+ 'user-agent' : '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'
+ }
+ };
+
+ it('Can be called with UAParser(ua)', function () {
+ let engine = UAParser(ua).engine;
+ assert.strictEqual(engine.name, "Trident");
+ });
+
+ it('Can be called with UAParser(ua, extensions)', function () {
+ let engine = UAParser(ua, ext).engine;
+ assert.strictEqual(engine.name, "Custom Browser 1");
+ });
+
+ it('Can be called with UAParser(ua, extensions, headers)', function () {
+ let engine = UAParser(ua, ext, req.headers).engine;
+ assert.strictEqual(engine.name, "Custom Browser 1");
+ });
+
+ it('Can be called with UAParser(ua, headers)', function () {
+ let engine = UAParser(ua, req.headers).engine;
+ assert.strictEqual(engine.name, "Trident");
+ });
+
+ it('Can be called with UAParser(extensions, headers)', function () {
+ let engine = UAParser(ext, req.headers).engine;
+ assert.strictEqual(engine.name, "Custom Browser 2");
+ });
+
+ it('Can be called with UAParser(headers)', function () {
+ let engine = UAParser(req.headers).engine;
+ 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);
+ });
+});
diff --git a/test/unit/ua-ch.js b/test/unit/ua-ch.js
new file mode 100644
index 000000000..7d12d5023
--- /dev/null
+++ b/test/unit/ua-ch.js
@@ -0,0 +1,635 @@
+const assert = require('assert');
+const { UAParser } = require('../../src/main/ua-parser');
+
+describe('Map UA-CH headers', () => {
+
+ const headers = {
+ 'sec-ch-ua' : '"Chromium";v="93", "Google Chrome";v="93", " Not;A Brand";v="99"',
+ 'sec-ch-ua-full-version-list' : '"Chromium";v="93.0.1.2", "Google Chrome";v="93.0.1.2", " Not;A Brand";v="99.0.1.2"',
+ 'sec-ch-ua-arch' : '"arm"',
+ 'sec-ch-ua-bitness' : '"64"',
+ 'sec-ch-ua-mobile' : '?1',
+ 'sec-ch-ua-model' : '"Pixel 99"',
+ 'sec-ch-ua-platform' : '"Windows"',
+ 'sec-ch-ua-platform-version' : '"13"',
+ 'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
+ };
+
+ let uap = UAParser(headers).withClientHints();
+ let browser = new UAParser(headers).getBrowser().withClientHints();
+ let cpu = new UAParser(headers).getCPU().withClientHints();
+ let device = new UAParser(headers).getDevice().withClientHints();
+ let engine = new UAParser(headers).getEngine().withClientHints();
+ let os = new UAParser(headers).getOS().withClientHints();
+
+ it('Can read from client-hints headers using `withClientHints()`', () => {
+
+ assert.strictEqual(uap.ua, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36");
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, "93.0.1.2");
+ assert.strictEqual(uap.browser.major, "93");
+ assert.strictEqual(browser.name, "Chrome");
+ assert.strictEqual(browser.version, "93.0.1.2");
+ assert.strictEqual(browser.major, "93");
+ assert.strictEqual(uap.cpu.architecture, "arm64");
+ assert.strictEqual(cpu.architecture, "arm64");
+ assert.strictEqual(uap.device.type, "mobile");
+ assert.strictEqual(uap.device.model, "Pixel 99");
+ assert.strictEqual(uap.device.vendor, "Google");
+ assert.strictEqual(device.type, "mobile");
+ assert.strictEqual(device.model, "Pixel 99");
+ assert.strictEqual(device.vendor, "Google");
+ assert.strictEqual(uap.engine.name, 'Blink');
+ assert.strictEqual(uap.engine.version, '93.0.1.2');
+ assert.strictEqual(engine.name, 'Blink');
+ assert.strictEqual(engine.version, '93.0.1.2');
+ assert.strictEqual(uap.os.name, "Windows");
+ assert.strictEqual(uap.os.version, "11");
+ assert.strictEqual(os.name, "Windows");
+ assert.strictEqual(os.version, "11");
+ });
+
+ it('Only read from user-agent header when called without `withClientHints()`', () => {
+
+ uap = UAParser(headers);
+ browser = new UAParser(headers).getBrowser();
+ cpu = new UAParser(headers).getCPU();
+ device = new UAParser(headers).getDevice();
+ engine = new UAParser(headers).getEngine();
+ os = new UAParser(headers).getOS();
+
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, "110.0.0.0");
+ assert.strictEqual(uap.browser.major, "110");
+ assert.strictEqual(uap.cpu.architecture, "amd64");
+ assert.strictEqual(uap.device.type, undefined);
+ assert.strictEqual(uap.device.model, undefined);
+ assert.strictEqual(uap.device.vendor, undefined);
+ assert.strictEqual(uap.engine.name, 'Blink');
+ assert.strictEqual(uap.engine.version, '110.0.0.0');
+ assert.strictEqual(uap.os.name, "Linux");
+ assert.strictEqual(uap.os.version, undefined);
+ });
+
+ it('Fallback to user-agent header when using `withClientHints()` but found no client hints-related headers', () => {
+
+ const headers2 = {
+ 'sec-ch-ua-mobile' : '?1',
+ 'user-agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
+ };
+
+ uap = UAParser(headers2).withClientHints();
+
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, "110.0.0.0");
+ assert.strictEqual(uap.browser.major, "110");
+ assert.strictEqual(uap.cpu.architecture, "amd64");
+ assert.strictEqual(uap.device.type, "mobile");
+ assert.strictEqual(uap.device.model, undefined);
+ assert.strictEqual(uap.device.vendor, undefined);
+ assert.strictEqual(uap.engine.name, 'Blink');
+ assert.strictEqual(uap.engine.version, '110.0.0.0');
+ assert.strictEqual(uap.os.name, "Linux");
+ assert.strictEqual(uap.os.version, undefined);
+ });
+
+ it('Can detect Apple silicon from client hints data', () => {
+
+ // https://github.com/faisalman/ua-parser-js/issues/489#issuecomment-1479213579
+ const httpHeadersFromAppleSilicon = {
+ 'sec-ch-ua-arch' : 'arm',
+ 'sec-ch-ua-platform' : 'macOS',
+ 'sec-ch-ua-mobile' : '?0',
+ 'sec-ch-ua' : '"Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
+ 'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0'
+ };
+
+ UAParser(httpHeadersFromAppleSilicon).withClientHints().then(ua => {
+
+ // Only works in Chrome
+ /*
+ if (ua.os.is("macOS") &&
+ ua.cpu.is("arm") &&
+ !ua.device.is("mobile") &&
+ !ua.device.is("tablet")) {
+ // possibly an Apple silicon device
+ }
+ */
+
+ assert.strictEqual(ua.os.is("macOS"), true);
+ assert.strictEqual(ua.cpu.is("arm"), true);
+ assert.strictEqual(ua.device.is("mobile"), false);
+ assert.strictEqual(ua.device.is("tablet"), false);
+ });
+ });
+
+ it('Can detect form-factors from client-hints', () => {
+
+ const FFVR = {
+ 'sec-ch-ua-form-factors' : '"VR"'
+ };
+
+ const FFEInk = {
+ 'sec-ch-ua-form-factors' : '"Tablet", "EInk"'
+ };
+
+ const FFUnknown = {
+ 'sec-ch-ua-form-factors' : '"Unknown"'
+ };
+
+ UAParser(FFVR).withClientHints().then(ua => {
+ assert.strictEqual(ua.device.type, 'xr');
+ });
+
+ UAParser(FFEInk).withClientHints().then(ua => {
+ assert.strictEqual(ua.device.type, 'tablet');
+ });
+
+
+ UAParser(FFUnknown).withClientHints().then(ua => {
+ assert.strictEqual(ua.device.type, undefined);
+ });
+ });
+
+ it('Avoid error on headers variation', () => {
+
+ const headers2 = {
+ 'sec-ch-ua' : '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
+ 'sec-ch-ua-full-version-list' : '"Google Chrome", "Chromium", "Not?A_Brand";v="24.0.0.0"',
+ 'sec-ch-ua-full-version' : '""',
+ 'sec-ch-ua-mobile' : '?0',
+ 'sec-ch-ua-arch' : '""',
+ 'sec-ch-ua-bitness' : '""',
+ 'sec-ch-ua-model' : '""',
+ 'sec-ch-ua-platform' : '"Windows"',
+ 'sec-ch-ua-platform-version' : '""',
+ 'sec-ch-ua-wow64' : '?0',
+ };
+
+ uap = UAParser(headers2).withClientHints();
+
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, undefined);
+ assert.strictEqual(uap.browser.major, undefined);
+ });
+
+ it('Prioritize more specific brand name regardless the order', () => {
+
+ const headers3a = {
+ 'sec-ch-ua-full-version-list' : '"Not_A Brand;v=8, Chromium;v=120.0.6099.131, Google Chrome;v=120.0.6099.132"'
+ };
+ const headers3b = {
+ 'sec-ch-ua-full-version-list' : '"Chromium;v=120.0.6099.131, Not_A Brand;v=8, Google Chrome;v=120.0.6099.132"'
+ };
+ const headers3c = {
+ 'sec-ch-ua-full-version-list' : '"Google Chrome;v=120.0.6099.132, Chromium;v=120.0.6099.131, Not_A Brand;v=8"'
+ };
+ const headers3d = {
+ 'sec-ch-ua-full-version-list' : '"Microsoft Edge;v=120.0.6099.133, Google Chrome;v=120.0.6099.132, Chromium;v=120.0.6099.131, Not_A Brand;v=8"'
+ };
+ const headers3e = {
+ 'sec-ch-ua-full-version-list' : '"Chromium;v=120.0.6099.131, Google Chrome;v=120.0.6099.132, Microsoft Edge;v=120.0.6099.133, Not_A Brand;v=8"'
+ };
+ const headers3f = {
+ 'sec-ch-ua-full-version-list' : '"Not_A Brand;v=8, Microsoft Edge;v=120.0.6099.133, Google Chrome;v=120.0.6099.132, Chromium;v=120.0.6099.131"'
+ };
+
+ uap = UAParser(headers3a).withClientHints();
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, "120.0.6099.132");
+
+ uap = UAParser(headers3b).withClientHints();
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, "120.0.6099.132");
+
+ uap = UAParser(headers3c).withClientHints();
+ assert.strictEqual(uap.browser.name, "Chrome");
+ assert.strictEqual(uap.browser.version, "120.0.6099.132");
+
+ uap = UAParser(headers3d).withClientHints();
+ assert.strictEqual(uap.browser.name, "Edge");
+ assert.strictEqual(uap.browser.version, "120.0.6099.133");
+
+ uap = UAParser(headers3e).withClientHints();
+ assert.strictEqual(uap.browser.name, "Edge");
+ assert.strictEqual(uap.browser.version, "120.0.6099.133");
+
+ uap = UAParser(headers3f).withClientHints();
+ assert.strictEqual(uap.browser.name, "Edge");
+ assert.strictEqual(uap.browser.version, "120.0.6099.133");
+ });
+});
+
+describe('UA-CH Headers tests', () => {
+ [
+ {
+ headers : {
+ 'sec-ch-ua': '"Avast Secure Browser";v="131", "Chromium";v="131", "Not_A Brand";v="24"'
+ },
+ expect: {
+ browser : {
+ name : 'Avast Secure Browser',
+ version : '131',
+ major : '131',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Not A(Brand";v="8", "Chromium";v="132", "Brave";v="132"'
+ },
+ expect: {
+ browser : {
+ name : 'Brave',
+ version : '132',
+ major : '132',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"'
+ },
+ expect: {
+ browser : {
+ name : 'Chrome',
+ version : '111',
+ major : '111',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Chromium";v="124", "HeadlessChrome";v="124", "Not-A.Brand";v="99"'
+ },
+ expect: {
+ browser : {
+ name : 'Chrome Headless',
+ version : '124',
+ major : '124',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Android WebView";v="123", "Not:A-Brand";v="8", "Chromium";v="123"'
+ },
+ expect: {
+ browser : {
+ name : 'Chrome WebView',
+ version : '123',
+ major : '123',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"DuckDuckGo";v="131", "Chromium";v="131", "Not_A Brand";v="24"'
+ },
+ expect : {
+ browser : {
+ name : 'DuckDuckGo',
+ version : '131',
+ major : '131',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Microsoft Edge";v="120"'
+ },
+ expect: {
+ browser : {
+ name : 'Edge',
+ version : '120',
+ major : '120',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '" Not;A Brand";v="99", "Microsoft Edge";v="103", "Chromium";v="103", "Microsoft Edge WebView2";v="104"'
+ },
+ expect: {
+ browser : {
+ name : 'Edge WebView2',
+ version : '104',
+ major : '104',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "HuaweiBrowser";v="114"'
+ },
+ expect: {
+ browser : {
+ name : 'Huawei Browser',
+ version : '114',
+ major : '114',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Miui Browser";v="123", "Not:A-Brand";v="8", "Chromium";v="123"'
+ },
+ expect: {
+ browser : {
+ name : 'MIUI Browser',
+ version : '123',
+ major : '123',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Chromium";v="130", "Oculus Browser";v="36", "Not?A_Brand";v="99"'
+ },
+ expect: {
+ browser : {
+ name : 'Oculus Browser',
+ version : '36',
+ major : '36',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Opera";v="116", "Chromium";v="131", "Not_A Brand";v="24"'
+ },
+ expect: {
+ browser : {
+ name : 'Opera',
+ version : '116',
+ major : '116',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Opera GX";v="114"'
+ },
+ expect: {
+ browser : {
+ name : 'Opera GX',
+ version : '114',
+ major : '114',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"OperaMobile";v="86", ";Not A Brand";v="99", "Opera";v="115", "Chromium";v="130"'
+ },
+ expect: {
+ browser : {
+ name : 'Opera Mobi',
+ version : '86',
+ major : '86',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Chromium";v="132", "OperaMobile";v="87", "Opera";v="117", " Not A;Brand";v="99"'
+ },
+ expect: {
+ browser : {
+ name : 'Opera Mobi',
+ version : '87',
+ major : '87',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Chromium";v="125", "Not.A/Brand";v="24", "Samsung Internet";v="27.0"'
+ },
+ expect: {
+ browser : {
+ name : 'Samsung Internet',
+ version : '27.0',
+ major : '27',
+ type : undefined
+ }
+ }
+ },
+ {
+ headers : {
+ 'sec-ch-ua': '"Chromium";v="130", "YaBrowser";v="24.12", "Not?A_Brand";v="99", "Yowser";v="2.5"'
+ },
+ expect: {
+ browser : {
+ name : 'Yandex',
+ version : '24.12',
+ major : '24',
+ type : undefined
+ }
+ }
+ },
+ ]
+ .forEach(test => {
+ const { browser } = UAParser(test.headers).withClientHints();
+ assert.deepEqual(browser, test.expect.browser);
+ });
+});
+
+describe('Identify vendor & type of device from given model name', () => {
+ [
+ {
+ model: '220733SG',
+ expect: {
+ vendor : 'Xiaomi',
+ type : 'mobile'
+ }
+ },
+ {
+ model: '5087Z',
+ expect: {
+ vendor : 'TCL',
+ type : 'mobile'
+ }
+ },
+ {
+ model: '9137W',
+ expect: {
+ vendor : 'TCL',
+ type : 'tablet'
+ }
+ },
+ {
+ model: 'BE2015',
+ expect: {
+ vendor : 'OnePlus',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'CPH2389',
+ expect: {
+ vendor : 'OnePlus',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'Infinix X669C',
+ expect: {
+ vendor : 'Infinix',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'itel L6502',
+ expect: {
+ vendor : 'itel',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'Lenovo TB-X606F',
+ expect: {
+ vendor : 'Lenovo',
+ type : 'tablet'
+ }
+ },
+ {
+ model: 'LM-Q720',
+ expect: {
+ vendor : 'LG',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'M2003J15SC',
+ expect: {
+ vendor : 'Xiaomi',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'MAR-LX1A',
+ expect: {
+ vendor : 'Huawei',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'moto g(20)',
+ expect: {
+ vendor : 'Motorola',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'Nokia C210',
+ expect: {
+ vendor : 'Nokia',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'Pixel 8',
+ expect: {
+ vendor : 'Google',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'Redmi Note 9S',
+ expect: {
+ vendor : 'Xiaomi',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'RMX3830',
+ expect: {
+ vendor : 'Realme',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'SM-S536DL',
+ expect: {
+ vendor : 'Samsung',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'SM-S546VL',
+ expect: {
+ vendor : 'Samsung',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'SM-T875',
+ expect: {
+ vendor : 'Samsung',
+ type : 'tablet'
+ }
+ },
+ {
+ model: 'STK-L21',
+ expect: {
+ vendor : 'Huawei',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'T430W',
+ expect: {
+ vendor : 'TCL',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'TECNO KI5k',
+ expect: {
+ vendor : 'TECNO',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'vivo 1820',
+ expect: {
+ vendor : 'Vivo',
+ type : 'mobile'
+ }
+ },
+ {
+ model: 'Xbox',
+ expect: {
+ vendor : 'Microsoft',
+ type : 'console'
+ }
+ }
+ ]
+ .forEach(test => {
+ it(`Solve "${test.model}"`, () => {
+ const headers = {
+ 'sec-ch-ua-model' : test.model,
+ };
+ const { device } = UAParser(headers).withClientHints();
+ assert.strictEqual(device.model, test.model);
+ assert.strictEqual(device.vendor, test.expect.vendor);
+ assert.strictEqual(device.type, test.expect.type);
+ });
+ });
+});
\ No newline at end of file