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 +41 -0
- package/README.md +58 -1
- package/dist/index.cjs +132 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +132 -49
- 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.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
|
-
|
|
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" },
|
|
@@ -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: "
|
|
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 === "
|
|
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
|
-
}
|
|
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
|
|
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
|
}
|