ua-browser 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -22,6 +22,23 @@ Detect browser, OS, device type, rendering engine, CPU architecture, bots, headl
22
22
  - **TypeScript** — full type definitions with precise literal union types (`BrowserName`, `OsName`, etc.)
23
23
  - **Tree-shakeable** — named exports + `sideEffects: false`, unused code eliminated by Vite / Rollup / webpack 5+
24
24
 
25
+ ## Why ua-browser
26
+
27
+ UA strings lie — a phone in desktop mode, a headless browser, or an AI crawler can all masquerade as an ordinary user. ua-browser combines hardware signals and Client Hints to stay accurate when the UA string can't be trusted.
28
+
29
+ | Capability | ua-browser | ua-parser-js | bowser | detect-browser |
30
+ | :-- | :--: | :--: | :--: | :--: |
31
+ | UA string parsing | ✅ | ✅ | ✅ | ✅ |
32
+ | Zero dependencies | ✅ | ✅ | ✅ | ✅ |
33
+ | TypeScript native | ✅ | ✅ | ✅ | ✅ |
34
+ | Tree-shakeable | ✅ | ❌ | ✅ | ❌ |
35
+ | Hardware-signal device detection (accurate in desktop mode) | ✅ | ❌ | ❌ | ❌ |
36
+ | CPU architecture (Apple Silicon vs Intel) | ✅ | ❌ | ❌ | ❌ |
37
+ | SSR Client Hints | ✅ | ❌ | ❌ | ❌ |
38
+ | Headless browser detection | ✅ | ❌ | ❌ | ❌ |
39
+ | AI bot recognition (40+ rules) | ✅ | ❌ | ❌ | ❌ |
40
+ | Extended device types (TV / Console / XR) | ✅ | ❌ | ❌ | ❌ |
41
+
25
42
  ## Installation
26
43
 
27
44
  ```sh
@@ -79,6 +96,8 @@ if (result.device === 'Mobile') {
79
96
  }
80
97
  ```
81
98
 
99
+ > **Note**: `detect()` uses the Client Hints high-entropy API (`getHighEntropyValues`), which is only available in **HTTPS or localhost** contexts. On plain HTTP pages it degrades silently — browser version and OS version fall back to the frozen UA string values (e.g. Chrome reports `149.0.0.0`, macOS 26+ reports `10.15.7`).
100
+
82
101
  ### Browser (sync: `uaBrowser`)
83
102
 
84
103
  ```typescript
@@ -223,6 +242,28 @@ Highlights:
223
242
  - **Bots** — GPTBot, ClaudeBot, PerplexityBot, CCBot; messaging bots (Slack, Discord, Telegram, WhatsApp) and more
224
243
  - **Devices** — Mobile, Tablet, PC, TV (Samsung Smart TV, HbbTV), Console (PS5, Xbox, Switch), XR (Vision Pro, Quest)
225
244
 
245
+ ## FAQ
246
+
247
+ **How is ua-browser different from ua-parser-js?**
248
+
249
+ `ua-parser-js` focuses on parsing the UA string itself and has no hardware-signal collection. It misidentifies device type when a phone is in desktop mode or when the UA is spoofed. ua-browser adds WebGL renderer, Client Hints, CSS `safe-area-inset`, and sensor APIs to detect the actual hardware — plus 40+ AI bot rules and headless browser detection that `ua-parser-js` does not include.
250
+
251
+ **Does it work in Next.js / Nuxt / other SSR frameworks?**
252
+
253
+ Yes. `parseUA(ua)` is a pure function with no browser API dependencies — it runs in Node.js, Deno, and Edge Runtime as-is. Pair `parseHeaders()` with `ACCEPT_CH` to leverage Client Hints for precise architecture and platform data on the server.
254
+
255
+ **Can it detect mobile devices when the user has enabled desktop mode?**
256
+
257
+ Yes, when you use `uaBrowser.detect()` or `getEnvContext()`. These APIs collect CSS `safe-area-inset`, the Vibration API, and device pixel ratio to identify the actual hardware, independent of what the UA string declares.
258
+
259
+ **How do I detect GPT, Claude, or other AI crawler requests?**
260
+
261
+ Check the `isBot` and `botName` fields on the return value. Built-in rules cover GPTBot, ClaudeBot, PerplexityBot, CCBot, and messaging link-preview bots (Slack, Discord, Telegram, WhatsApp).
262
+
263
+ **What is the bundle size?**
264
+
265
+ Zero runtime dependencies. The bundle is tiny after gzip; tree-shaking named exports makes it smaller still.
266
+
226
267
  ## License
227
268
 
228
269
  [MIT](./LICENSE) © yangtianxia
package/README.md CHANGED
@@ -21,6 +21,23 @@
21
21
  - **TypeScript** — 完整类型定义,`BrowserName`、`OsName` 等均为精确字面量联合类型
22
22
  - **Tree-shakeable** — 所有功能按需导入,不引入多余代码
23
23
 
24
+ ## 为什么选 ua-browser
25
+
26
+ UA 字符串会撒谎 —— 开了桌面模式的手机、无头浏览器、AI 爬虫都可能伪装成普通用户。ua-browser 额外引入硬件信号与 Client Hints,在 UA 失真时依然准确。
27
+
28
+ | 能力 | ua-browser | ua-parser-js | bowser | detect-browser |
29
+ | :-- | :--: | :--: | :--: | :--: |
30
+ | UA 字符串解析 | ✅ | ✅ | ✅ | ✅ |
31
+ | 零依赖 | ✅ | ✅ | ✅ | ✅ |
32
+ | TypeScript 原生 | ✅ | ✅ | ✅ | ✅ |
33
+ | Tree-shakeable | ✅ | ❌ | ✅ | ❌ |
34
+ | 硬件信号设备检测(桌面模式下仍准确)| ✅ | ❌ | ❌ | ❌ |
35
+ | CPU 架构(Apple Silicon / Intel 区分)| ✅ | ❌ | ❌ | ❌ |
36
+ | SSR Client Hints | ✅ | ❌ | ❌ | ❌ |
37
+ | 无头浏览器检测 | ✅ | ❌ | ❌ | ❌ |
38
+ | AI 爬虫识别 | ✅ 40+ | ❌ | ❌ | ❌ |
39
+ | 设备类型(TV / Console / XR)| ✅ | ❌ | ❌ | ❌ |
40
+
24
41
  ## 安装
25
42
 
26
43
  ```sh
@@ -62,7 +79,25 @@ console.log(info)
62
79
 
63
80
  ## 使用
64
81
 
65
- ### 浏览器环境
82
+ ### 浏览器环境(推荐:`detect`)
83
+
84
+ 使用 `detect()` 获得精准设备与架构信息 —— 在 UA 解析的基础上额外采集硬件信号:
85
+
86
+ ```typescript
87
+ import uaBrowser from 'ua-browser'
88
+
89
+ const result = await uaBrowser.detect()
90
+ console.log(result.device) // 'Mobile' —— 即使开了桌面模式也正确
91
+ console.log(result.arch) // 'arm64' 或 'x86_64'
92
+
93
+ if (result.device === 'Mobile') {
94
+ // 跳转移动版
95
+ }
96
+ ```
97
+
98
+ > **注意**:`detect()` 内部调用 Client Hints 高熵 API(`getHighEntropyValues`),该 API 仅在 **HTTPS 或 localhost** 环境下可用。HTTP 页面中调用时会静默降级,浏览器版本和 OS 版本将退回 UA 字符串的冻结值(如 Chrome 版本显示为 `149.0.0.0`,macOS 26+ 显示为 `10.15.7`)。
99
+
100
+ ### 浏览器环境(同步:`uaBrowser`)
66
101
 
67
102
  ```typescript
68
103
  import uaBrowser from 'ua-browser'
@@ -205,6 +240,28 @@ import {
205
240
  - **AI 爬虫** — GPTBot、ClaudeBot、PerplexityBot、CCBot;消息应用 Bot(Slack、Discord、Telegram、WhatsApp)等
206
241
  - **设备** — Mobile、Tablet、PC、TV(含三星 Smart TV、HbbTV 标准)、Console(PS5、Xbox、Switch)、XR(Vision Pro、Quest)
207
242
 
243
+ ## 常见问题
244
+
245
+ **和 ua-parser-js 有什么区别?**
246
+
247
+ `ua-parser-js` 专注于 UA 字符串本身的解析,不具备硬件信号采集能力;在手机开启桌面模式或 UA 被篡改时会给出错误结果。ua-browser 额外引入 WebGL 渲染器、Client Hints、CSS `safe-area-inset` 等多维信号,并内置 40+ AI 爬虫识别规则和无头浏览器检测,`ua-parser-js` 均不支持。
248
+
249
+ **在 Next.js / Nuxt 等 SSR 框架里能用吗?**
250
+
251
+ 可以。`parseUA(ua)` 是纯函数,无任何浏览器 API 依赖,可直接在 Node.js / Edge Runtime 中使用。搭配 `parseHeaders()` 和 `ACCEPT_CH` 还可在服务端利用 Client Hints 获取精准的架构与平台信息。
252
+
253
+ **手机开了"请求桌面网站",还能正确识别设备类型吗?**
254
+
255
+ 可以,但需要使用 `uaBrowser.detect()` 或手动调用 `getEnvContext()`。这两种方式会采集 CSS `safe-area-inset`、振动 API、设备像素比等硬件信号,不依赖 UA 字符串里的设备声明。
256
+
257
+ **如何识别 GPT、Claude 等 AI 爬虫的抓取请求?**
258
+
259
+ 读取返回值的 `isBot` 和 `botName` 字段即可。库内置了 GPTBot、ClaudeBot、PerplexityBot、CCBot 等规则,同时也覆盖 Slack、Discord、Telegram 等消息应用的链接预览 Bot。
260
+
261
+ **包体积有多大?**
262
+
263
+ 零运行时依赖,gzip 后极小;按需引入(named exports + tree-shaking)体积更小。
264
+
208
265
  ## License
209
266
 
210
267
  [MIT](./LICENSE) © yangtianxia
package/dist/index.cjs CHANGED
@@ -4,25 +4,24 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  // package.json
6
6
  var package_default = {
7
- version: "1.4.0"};
7
+ version: "1.4.2"};
8
8
 
9
9
  // src/constants/os.ts
10
10
  var OS_DEFS = [
11
- { name: "WebOS", detect: /hpwOS/, versionPattern: /hpwOS\/([\d.]+)/ },
12
- { name: "Symbian", detect: /Symbian/, versionPattern: null },
13
- { name: "MeeGo", detect: /MeeGo/, versionPattern: null },
14
- { name: "BlackBerry", detect: /(BlackBerry|RIM)/, versionPattern: null },
15
- { name: "FreeBSD", detect: /FreeBSD/, versionPattern: null },
16
- { name: "Debian", detect: /Debian/, versionPattern: /Debian\/([\d.]+)/ },
17
- { name: "Ubuntu", detect: /Ubuntu/, versionPattern: null },
18
- // Linux must come before Chrome OS: Chrome OS UAs contain "X11", so Linux matches first,
19
- // then Chrome OS overrides it.
20
- { name: "Linux", detect: /(Linux|X11)/, versionPattern: null },
21
- { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
22
- { name: "Tizen", detect: /Tizen/, versionPattern: /Tizen ([\d.]+)/ },
23
- { name: "iOS", detect: /like Mac OS X/, versionPattern: /OS ([\d_]+) like/ },
11
+ { name: "WebOS", priority: 20, detect: /hpwOS/, versionPattern: /hpwOS\/([\d.]+)/ },
12
+ { name: "Symbian", priority: 20, detect: /Symbian/, versionPattern: null },
13
+ { name: "MeeGo", priority: 20, detect: /MeeGo/, versionPattern: null },
14
+ { name: "BlackBerry", priority: 20, detect: /(BlackBerry|RIM)/, versionPattern: null },
15
+ { name: "FreeBSD", priority: 20, detect: /FreeBSD/, versionPattern: null },
16
+ { name: "Debian", priority: 20, detect: /Debian/, versionPattern: /Debian\/([\d.]+)/ },
17
+ { name: "Ubuntu", priority: 20, detect: /Ubuntu/, versionPattern: null },
18
+ { name: "Linux", priority: 10, detect: /(Linux|X11)/, versionPattern: null },
19
+ { name: "Chrome OS", priority: 30, detect: /CrOS/, versionPattern: null },
20
+ { name: "Tizen", priority: 20, detect: /Tizen/, versionPattern: /Tizen ([\d.]+)/ },
21
+ { name: "iOS", priority: 20, detect: /like Mac OS X/, versionPattern: /OS ([\d_]+) like/ },
24
22
  {
25
23
  name: "MacOS",
24
+ priority: 20,
26
25
  detect: /Macintosh/,
27
26
  versionPattern: /Mac OS X -?([\d_.]+)/,
28
27
  versionNames: {
@@ -40,27 +39,21 @@ var OS_DEFS = [
40
39
  "15": "Sequoia"
41
40
  }
42
41
  },
43
- // visionOS / tvOS must come AFTER iOS: their UAs also contain "like Mac OS X",
44
- // so they need to override iOS via the last-match-wins iteration.
45
- { name: "visionOS", detect: /visionOS/, versionPattern: /visionOS ([\d_]+)/ },
46
- { name: "tvOS", detect: /Apple TV/, versionPattern: /OS ([\d_]+) like/ },
47
- { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
48
- // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
49
- // first, then HarmonyOS overrides it. versionPattern tries direct extraction first (5.0+
50
- // pure HarmonyOS UAs don't have Android token), then falls back to Android version + lookup.
42
+ { name: "visionOS", priority: 30, detect: /visionOS/, versionPattern: /visionOS ([\d_]+)/ },
43
+ { name: "tvOS", priority: 30, detect: /Apple TV/, versionPattern: /OS ([\d_]+) like/ },
44
+ { name: "Android", priority: 20, detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
51
45
  {
52
46
  name: "HarmonyOS",
47
+ priority: 30,
53
48
  detect: /HarmonyOS/,
54
49
  versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
55
- versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
50
+ versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4", "14": "4" }
56
51
  },
57
- // OpenHarmony (open-source base) must come after HarmonyOS to override any earlier match.
58
- { name: "OpenHarmony", detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
59
- { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
60
- // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
61
- // matches first, then Windows Phone overrides it.
52
+ { name: "OpenHarmony", priority: 30, detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
53
+ { name: "KaiOS", priority: 30, detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
62
54
  {
63
55
  name: "Windows",
56
+ priority: 10,
64
57
  detect: /Windows/,
65
58
  versionPattern: /Windows NT ([\d.]+)/,
66
59
  versionLookup: {
@@ -82,7 +75,7 @@ var OS_DEFS = [
82
75
  "11": "Windows 11"
83
76
  }
84
77
  },
85
- { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
78
+ { name: "Windows Phone", priority: 30, detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
86
79
  ];
87
80
 
88
81
  // src/constants/browsers.ts
@@ -304,9 +297,11 @@ function lookupVersionName(map, version) {
304
297
  }
305
298
  function detectOs(ua, windowsVersion) {
306
299
  let matchedDef = null;
300
+ let bestPriority = -1;
307
301
  for (const def of OS_DEFS) {
308
- if (def.detect.test(ua)) {
302
+ if (def.detect.test(ua) && def.priority > bestPriority) {
309
303
  matchedDef = def;
304
+ bestPriority = def.priority;
310
305
  }
311
306
  }
312
307
  if (!matchedDef) return { os: "unknown", osVersion: "unknown", osVersionName: "unknown" };
@@ -455,7 +450,7 @@ var BOT_DEFS = [
455
450
  { name: "Facebookbot", detect: /(facebookexternalhit|FacebookBot)/, category: "social" },
456
451
  { name: "Twitterbot", detect: /Twitterbot/, category: "social" },
457
452
  { name: "LinkedInBot", detect: /LinkedInBot/, category: "social" },
458
- { name: "PinterestBot", detect: /Pinterest/, category: "social" },
453
+ { name: "PinterestBot", detect: /Pinterestbot/i, category: "social" },
459
454
  // Messaging link preview bots
460
455
  { name: "Slackbot", detect: /Slackbot/, category: "link-preview" },
461
456
  { name: "Discordbot", detect: /Discordbot/, category: "link-preview" },
@@ -472,14 +467,19 @@ var BOT_DEFS = [
472
467
  { name: "OAI-SearchBot", detect: /OAI-SearchBot/, category: "ai-llm" },
473
468
  { name: "ChatGPT-User", detect: /ChatGPT-User/, category: "ai-llm" },
474
469
  { name: "ClaudeBot", detect: /ClaudeBot/, category: "ai-llm" },
470
+ { name: "Claude-User", detect: /Claude-User/, category: "ai-llm" },
471
+ { name: "Claude-SearchBot", detect: /Claude-SearchBot/, category: "ai-llm" },
475
472
  { name: "PerplexityBot", detect: /PerplexityBot/, category: "ai-llm" },
476
473
  { name: "CCBot", detect: /CCBot/, category: "ai-llm" },
477
- { name: "AdsBot", detect: /AdsBot-Google/, category: "ai-llm" },
474
+ { name: "AdsBot", detect: /AdsBot-Google/, category: "search-engine" },
478
475
  { name: "Google-Extended", detect: /Google-Extended/, category: "ai-llm" },
479
476
  { name: "Meta-ExternalAgent", detect: /meta-externalagent/i, category: "ai-llm" },
480
477
  { name: "Amazonbot", detect: /Amazonbot/, category: "ai-llm" },
481
478
  { name: "Diffbot", detect: /Diffbot/, category: "ai-llm" },
482
479
  { name: "cohere-ai", detect: /cohere-ai/, category: "ai-llm" },
480
+ { name: "MistralAI-User", detect: /MistralAI-User/, category: "ai-llm" },
481
+ { name: "DeepSeekBot", detect: /DeepSeekBot/, category: "ai-llm" },
482
+ { name: "XAI-Crawler", detect: /XAI-Crawler/, category: "ai-llm" },
483
483
  { name: "YouBot", detect: /YouBot/, category: "ai-llm" },
484
484
  // Monitoring / archiving
485
485
  { name: "UptimeRobot", detect: /UptimeRobot/, category: "monitoring" },
@@ -584,6 +584,7 @@ var BRAND_TO_BROWSER = [
584
584
  ["Microsoft Edge", "Edge"],
585
585
  ["Opera", "Opera"],
586
586
  ["Vivaldi", "Vivaldi"],
587
+ ["Brave", "Brave"],
587
588
  ["Google Chrome", "Chrome"],
588
589
  ["Chromium", "Chromium"]
589
590
  ];
@@ -648,6 +649,82 @@ function normalizeBCP47(raw) {
648
649
  return p.toUpperCase();
649
650
  }).join("-");
650
651
  }
652
+ var ISO_639_1 = /* @__PURE__ */ new Set([
653
+ "af",
654
+ "am",
655
+ "ar",
656
+ "az",
657
+ "be",
658
+ "bg",
659
+ "bn",
660
+ "bs",
661
+ "ca",
662
+ "cs",
663
+ "cy",
664
+ "da",
665
+ "de",
666
+ "el",
667
+ "en",
668
+ "es",
669
+ "et",
670
+ "eu",
671
+ "fa",
672
+ "fi",
673
+ "fr",
674
+ "ga",
675
+ "gl",
676
+ "gu",
677
+ "he",
678
+ "hi",
679
+ "hr",
680
+ "hu",
681
+ "hy",
682
+ "id",
683
+ "is",
684
+ "it",
685
+ "ja",
686
+ "ka",
687
+ "kk",
688
+ "km",
689
+ "kn",
690
+ "ko",
691
+ "lt",
692
+ "lv",
693
+ "mk",
694
+ "ml",
695
+ "mn",
696
+ "mr",
697
+ "ms",
698
+ "mt",
699
+ "my",
700
+ "nb",
701
+ "ne",
702
+ "nl",
703
+ "no",
704
+ "pa",
705
+ "pl",
706
+ "pt",
707
+ "ro",
708
+ "ru",
709
+ "si",
710
+ "sk",
711
+ "sl",
712
+ "sq",
713
+ "sr",
714
+ "sv",
715
+ "sw",
716
+ "ta",
717
+ "te",
718
+ "th",
719
+ "tl",
720
+ "tr",
721
+ "uk",
722
+ "ur",
723
+ "uz",
724
+ "vi",
725
+ "zh",
726
+ "zu"
727
+ ]);
651
728
  function languageFromUA(ua) {
652
729
  const kwMatch = /\bLanguage\/([a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4}){1,2})\b/i.exec(ua);
653
730
  if (kwMatch) return normalizeBCP47(kwMatch[1]);
@@ -657,6 +734,10 @@ function languageFromUA(ua) {
657
734
  const parts = m[1].replace(/_/g, "-").split("-");
658
735
  if (parts.length >= 2) return normalizeBCP47(m[1]);
659
736
  }
737
+ const bare = /[;(]\s*([a-z]{2,3})\s*[;)]/g;
738
+ while ((m = bare.exec(ua)) !== null) {
739
+ if (ISO_639_1.has(m[1])) return m[1];
740
+ }
660
741
  return "unknown";
661
742
  }
662
743
  function parseUA(ua, options = {}) {
@@ -713,20 +794,8 @@ function parseUA(ua, options = {}) {
713
794
  const opVer = (_h = (_g = /OPR\/([\d.]+)/.exec(ua)) != null ? _g : /OPT\/([\d.]+)/.exec(ua)) != null ? _h : /Opera\/([\d.]+)/.exec(ua);
714
795
  version = (_i = opVer == null ? void 0 : opVer[1]) != null ? _i : "unknown";
715
796
  }
716
- if (browser === "Chrome" && /\S+Browser\//.test(ua)) {
717
- const m = /(\S+Browser)\/([\d.]+)/.exec(ua);
718
- if (m) {
719
- browser = m[1];
720
- version = m[2];
721
- }
722
- }
723
- if (browser === "Firefox" && nav) {
724
- try {
725
- if (typeof clientInformation !== "undefined" || typeof u2f === "undefined") {
726
- browser = "Firefox Nightly";
727
- }
728
- } catch (e) {
729
- }
797
+ if (browser === "Firefox" && /Firefox\/[\d.]+a\d/.test(ua)) {
798
+ browser = "Firefox Nightly";
730
799
  }
731
800
  if (os === "iOS" && browser === "Safari") {
732
801
  const m = /Version\/([\d.]+)/.exec(ua);
@@ -769,7 +838,8 @@ function parseUA(ua, options = {}) {
769
838
  }
770
839
  }
771
840
  const { engine, engineVersion } = detectEngine(ua, browser, version);
772
- const versionMajor = parseInt((_l = version.split(".")[0]) != null ? _l : "0", 10) || 0;
841
+ const major = parseInt((_l = version.split(".")[0]) != null ? _l : "", 10);
842
+ const versionMajor = Number.isNaN(major) ? 0 : major;
773
843
  const connectionType = (_p = (_o = (_n = (_m = options.ctx) != null ? _m : options.nav) == null ? void 0 : _n.connection) == null ? void 0 : _o.effectiveType) != null ? _p : "unknown";
774
844
  const finalOsVersionName = os === "MacOS" || os === "Windows" ? (() => {
775
845
  var _a2;
@@ -1010,7 +1080,7 @@ function deriveWindowsVersion2(platformVersion) {
1010
1080
  return isNaN(major) ? null : major >= 13 ? "11" : "10";
1011
1081
  }
1012
1082
  function parseHeaders(headers) {
1013
- var _a, _b, _c, _d, _e;
1083
+ var _a, _b, _c, _d, _e, _f;
1014
1084
  const normalised = {};
1015
1085
  for (const key of Object.keys(headers)) {
1016
1086
  normalised[key.toLowerCase()] = headers[key];
@@ -1027,12 +1097,24 @@ function parseHeaders(headers) {
1027
1097
  const model = unquote(get("sec-ch-ua-model"));
1028
1098
  const platformVersion = unquote(get("sec-ch-ua-platform-version"));
1029
1099
  const platform = (_e = unquote(get("sec-ch-ua-platform"))) != null ? _e : "";
1100
+ const fullVersionListRaw = get("sec-ch-ua-full-version-list");
1101
+ const fullVersionList = [];
1102
+ if (fullVersionListRaw) {
1103
+ const re = /"([^"]+)";v="([^"]+)"/g;
1104
+ let m;
1105
+ while ((m = re.exec(fullVersionListRaw)) !== null) {
1106
+ fullVersionList.push({ brand: m[1], version: m[2] });
1107
+ }
1108
+ }
1030
1109
  const highEntropyData = {};
1031
1110
  if (architecture !== void 0) highEntropyData.architecture = architecture;
1032
1111
  if (bitness !== void 0) highEntropyData.bitness = bitness;
1033
1112
  if (model !== void 0) highEntropyData.model = model;
1034
1113
  if (platformVersion !== void 0) highEntropyData.platformVersion = platformVersion;
1114
+ if (fullVersionList.length > 0) highEntropyData.fullVersionList = fullVersionList;
1035
1115
  const isMobile = get("sec-ch-ua-mobile") === "?1";
1116
+ const secCHUA = (_f = get("sec-ch-ua")) != null ? _f : "";
1117
+ const hasBrave = /"Brave"/.test(secCHUA);
1036
1118
  const windowsVersion = platform === "Windows" ? deriveWindowsVersion2(platformVersion) : null;
1037
1119
  const ctx = {
1038
1120
  userAgent: ua,
@@ -1040,7 +1122,8 @@ function parseHeaders(headers) {
1040
1122
  language,
1041
1123
  maxTouchPoints: isMobile ? 1 : 0,
1042
1124
  highEntropyData: Object.keys(highEntropyData).length > 0 ? highEntropyData : void 0,
1043
- windowsVersion
1125
+ windowsVersion,
1126
+ hasBrave
1044
1127
  };
1045
1128
  return parseUA(ua, { ctx });
1046
1129
  }