ua-browser 1.4.0 → 1.4.1
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 +41 -0
- package/README.md +58 -1
- package/dist/index.cjs +126 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +126 -48
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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.
|
|
7
|
+
version: "1.4.1"};
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
{ name: "
|
|
21
|
-
{ name: "
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
{ name: "
|
|
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
|
-
|
|
58
|
-
{ name: "
|
|
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: /
|
|
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" },
|
|
@@ -584,6 +579,7 @@ var BRAND_TO_BROWSER = [
|
|
|
584
579
|
["Microsoft Edge", "Edge"],
|
|
585
580
|
["Opera", "Opera"],
|
|
586
581
|
["Vivaldi", "Vivaldi"],
|
|
582
|
+
["Brave", "Brave"],
|
|
587
583
|
["Google Chrome", "Chrome"],
|
|
588
584
|
["Chromium", "Chromium"]
|
|
589
585
|
];
|
|
@@ -648,6 +644,82 @@ function normalizeBCP47(raw) {
|
|
|
648
644
|
return p.toUpperCase();
|
|
649
645
|
}).join("-");
|
|
650
646
|
}
|
|
647
|
+
var ISO_639_1 = /* @__PURE__ */ new Set([
|
|
648
|
+
"af",
|
|
649
|
+
"am",
|
|
650
|
+
"ar",
|
|
651
|
+
"az",
|
|
652
|
+
"be",
|
|
653
|
+
"bg",
|
|
654
|
+
"bn",
|
|
655
|
+
"bs",
|
|
656
|
+
"ca",
|
|
657
|
+
"cs",
|
|
658
|
+
"cy",
|
|
659
|
+
"da",
|
|
660
|
+
"de",
|
|
661
|
+
"el",
|
|
662
|
+
"en",
|
|
663
|
+
"es",
|
|
664
|
+
"et",
|
|
665
|
+
"eu",
|
|
666
|
+
"fa",
|
|
667
|
+
"fi",
|
|
668
|
+
"fr",
|
|
669
|
+
"ga",
|
|
670
|
+
"gl",
|
|
671
|
+
"gu",
|
|
672
|
+
"he",
|
|
673
|
+
"hi",
|
|
674
|
+
"hr",
|
|
675
|
+
"hu",
|
|
676
|
+
"hy",
|
|
677
|
+
"id",
|
|
678
|
+
"is",
|
|
679
|
+
"it",
|
|
680
|
+
"ja",
|
|
681
|
+
"ka",
|
|
682
|
+
"kk",
|
|
683
|
+
"km",
|
|
684
|
+
"kn",
|
|
685
|
+
"ko",
|
|
686
|
+
"lt",
|
|
687
|
+
"lv",
|
|
688
|
+
"mk",
|
|
689
|
+
"ml",
|
|
690
|
+
"mn",
|
|
691
|
+
"mr",
|
|
692
|
+
"ms",
|
|
693
|
+
"mt",
|
|
694
|
+
"my",
|
|
695
|
+
"nb",
|
|
696
|
+
"ne",
|
|
697
|
+
"nl",
|
|
698
|
+
"no",
|
|
699
|
+
"pa",
|
|
700
|
+
"pl",
|
|
701
|
+
"pt",
|
|
702
|
+
"ro",
|
|
703
|
+
"ru",
|
|
704
|
+
"si",
|
|
705
|
+
"sk",
|
|
706
|
+
"sl",
|
|
707
|
+
"sq",
|
|
708
|
+
"sr",
|
|
709
|
+
"sv",
|
|
710
|
+
"sw",
|
|
711
|
+
"ta",
|
|
712
|
+
"te",
|
|
713
|
+
"th",
|
|
714
|
+
"tl",
|
|
715
|
+
"tr",
|
|
716
|
+
"uk",
|
|
717
|
+
"ur",
|
|
718
|
+
"uz",
|
|
719
|
+
"vi",
|
|
720
|
+
"zh",
|
|
721
|
+
"zu"
|
|
722
|
+
]);
|
|
651
723
|
function languageFromUA(ua) {
|
|
652
724
|
const kwMatch = /\bLanguage\/([a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4}){1,2})\b/i.exec(ua);
|
|
653
725
|
if (kwMatch) return normalizeBCP47(kwMatch[1]);
|
|
@@ -657,6 +729,10 @@ function languageFromUA(ua) {
|
|
|
657
729
|
const parts = m[1].replace(/_/g, "-").split("-");
|
|
658
730
|
if (parts.length >= 2) return normalizeBCP47(m[1]);
|
|
659
731
|
}
|
|
732
|
+
const bare = /[;(]\s*([a-z]{2,3})\s*[;)]/g;
|
|
733
|
+
while ((m = bare.exec(ua)) !== null) {
|
|
734
|
+
if (ISO_639_1.has(m[1])) return m[1];
|
|
735
|
+
}
|
|
660
736
|
return "unknown";
|
|
661
737
|
}
|
|
662
738
|
function parseUA(ua, options = {}) {
|
|
@@ -713,20 +789,8 @@ function parseUA(ua, options = {}) {
|
|
|
713
789
|
const opVer = (_h = (_g = /OPR\/([\d.]+)/.exec(ua)) != null ? _g : /OPT\/([\d.]+)/.exec(ua)) != null ? _h : /Opera\/([\d.]+)/.exec(ua);
|
|
714
790
|
version = (_i = opVer == null ? void 0 : opVer[1]) != null ? _i : "unknown";
|
|
715
791
|
}
|
|
716
|
-
if (browser === "
|
|
717
|
-
|
|
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
|
-
}
|
|
792
|
+
if (browser === "Firefox" && /Firefox\/[\d.]+a\d/.test(ua)) {
|
|
793
|
+
browser = "Firefox Nightly";
|
|
730
794
|
}
|
|
731
795
|
if (os === "iOS" && browser === "Safari") {
|
|
732
796
|
const m = /Version\/([\d.]+)/.exec(ua);
|
|
@@ -769,7 +833,8 @@ function parseUA(ua, options = {}) {
|
|
|
769
833
|
}
|
|
770
834
|
}
|
|
771
835
|
const { engine, engineVersion } = detectEngine(ua, browser, version);
|
|
772
|
-
const
|
|
836
|
+
const major = parseInt((_l = version.split(".")[0]) != null ? _l : "", 10);
|
|
837
|
+
const versionMajor = Number.isNaN(major) ? 0 : major;
|
|
773
838
|
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
839
|
const finalOsVersionName = os === "MacOS" || os === "Windows" ? (() => {
|
|
775
840
|
var _a2;
|
|
@@ -1010,7 +1075,7 @@ function deriveWindowsVersion2(platformVersion) {
|
|
|
1010
1075
|
return isNaN(major) ? null : major >= 13 ? "11" : "10";
|
|
1011
1076
|
}
|
|
1012
1077
|
function parseHeaders(headers) {
|
|
1013
|
-
var _a, _b, _c, _d, _e;
|
|
1078
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1014
1079
|
const normalised = {};
|
|
1015
1080
|
for (const key of Object.keys(headers)) {
|
|
1016
1081
|
normalised[key.toLowerCase()] = headers[key];
|
|
@@ -1027,12 +1092,24 @@ function parseHeaders(headers) {
|
|
|
1027
1092
|
const model = unquote(get("sec-ch-ua-model"));
|
|
1028
1093
|
const platformVersion = unquote(get("sec-ch-ua-platform-version"));
|
|
1029
1094
|
const platform = (_e = unquote(get("sec-ch-ua-platform"))) != null ? _e : "";
|
|
1095
|
+
const fullVersionListRaw = get("sec-ch-ua-full-version-list");
|
|
1096
|
+
const fullVersionList = [];
|
|
1097
|
+
if (fullVersionListRaw) {
|
|
1098
|
+
const re = /"([^"]+)";v="([^"]+)"/g;
|
|
1099
|
+
let m;
|
|
1100
|
+
while ((m = re.exec(fullVersionListRaw)) !== null) {
|
|
1101
|
+
fullVersionList.push({ brand: m[1], version: m[2] });
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1030
1104
|
const highEntropyData = {};
|
|
1031
1105
|
if (architecture !== void 0) highEntropyData.architecture = architecture;
|
|
1032
1106
|
if (bitness !== void 0) highEntropyData.bitness = bitness;
|
|
1033
1107
|
if (model !== void 0) highEntropyData.model = model;
|
|
1034
1108
|
if (platformVersion !== void 0) highEntropyData.platformVersion = platformVersion;
|
|
1109
|
+
if (fullVersionList.length > 0) highEntropyData.fullVersionList = fullVersionList;
|
|
1035
1110
|
const isMobile = get("sec-ch-ua-mobile") === "?1";
|
|
1111
|
+
const secCHUA = (_f = get("sec-ch-ua")) != null ? _f : "";
|
|
1112
|
+
const hasBrave = /"Brave"/.test(secCHUA);
|
|
1036
1113
|
const windowsVersion = platform === "Windows" ? deriveWindowsVersion2(platformVersion) : null;
|
|
1037
1114
|
const ctx = {
|
|
1038
1115
|
userAgent: ua,
|
|
@@ -1040,7 +1117,8 @@ function parseHeaders(headers) {
|
|
|
1040
1117
|
language,
|
|
1041
1118
|
maxTouchPoints: isMobile ? 1 : 0,
|
|
1042
1119
|
highEntropyData: Object.keys(highEntropyData).length > 0 ? highEntropyData : void 0,
|
|
1043
|
-
windowsVersion
|
|
1120
|
+
windowsVersion,
|
|
1121
|
+
hasBrave
|
|
1044
1122
|
};
|
|
1045
1123
|
return parseUA(ua, { ctx });
|
|
1046
1124
|
}
|