ua-browser 1.0.0 → 1.0.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.md CHANGED
@@ -1,27 +1,39 @@
1
- # uaBrowser - 浏览器检测
1
+ # ua-browser
2
2
 
3
- 通过 User Agent 和浏览器环境变量检测浏览器、系统及设备类型,支持 Node.js 环境,零依赖。
3
+ [![npm version](https://img.shields.io/npm/v/ua-browser?color=cb3837)](https://www.npmjs.com/package/ua-browser)
4
+ [![npm downloads](https://img.shields.io/npm/dm/ua-browser)](https://www.npmjs.com/package/ua-browser)
5
+ [![license](https://img.shields.io/npm/l/ua-browser)](./LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ready-3178c6)](https://www.typescriptlang.org/)
4
7
 
5
- **[📖 文档](https://yangtianxia.github.io/ua-browser/)** · **[🎮 Playground](https://yangtianxia.github.io/ua-browser/playground)**
8
+ Detect browser, OS, device type, rendering engine, CPU architecture, bots, and headless browsers from User Agent strings. Zero dependencies. Works in both browser and Node.js environments.
6
9
 
7
- ## 安装
10
+ **[📖 Documentation](https://yangtianxia.github.io/ua-browser/)** · **[🎮 Playground](https://yangtianxia.github.io/ua-browser/playground)** · **[中文](./README.zh-CN.md)**
11
+
12
+ ## Features
13
+
14
+ - **Comprehensive detection** — browser, OS, engine, device type (Mobile / Tablet / TV / PC), CPU arch, bots, headless browsers
15
+ - **AI bot recognition** — built-in support for GPTBot, ClaudeBot, PerplexityBot, CCBot and more
16
+ - **Zero dependencies** — no runtime dependencies, tiny bundle size after gzip
17
+ - **Pure function** — `parseUA()` has no global state, works seamlessly with SSR / Node.js
18
+ - **TypeScript** — full type definitions with precise literal union types (`BrowserName`, `OsName`, etc.)
19
+ - **Tree-shakeable** — named exports + `sideEffects: false`, unused code eliminated by Vite / Rollup / webpack 5+
20
+
21
+ ## Installation
8
22
 
9
23
  ```sh
10
24
  npm i ua-browser
11
- # or
25
+ # pnpm
12
26
  pnpm add ua-browser
13
- # or
27
+ # yarn
14
28
  yarn add ua-browser
15
29
  ```
16
30
 
17
- ## 快速开始
31
+ ## Quick Start
18
32
 
19
33
  ```typescript
20
34
  import uaBrowser from 'ua-browser'
21
35
 
22
36
  const info = uaBrowser()
23
- // 或传入自定义 UA 字符串
24
- const info = uaBrowser('Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...')
25
37
 
26
38
  console.log(info)
27
39
  // {
@@ -36,47 +48,129 @@ console.log(info)
36
48
  // isHeadless: false,
37
49
  // isBot: false,
38
50
  // botName: 'unknown',
39
- // language: 'zh-CN',
51
+ // language: 'en-US',
40
52
  // platform: 'Win32'
41
53
  // }
42
54
  ```
43
55
 
44
- ## 命名导出(tree-shakeable)
56
+ > Pass a custom UA string: `uaBrowser('Mozilla/5.0 ...')`
57
+
58
+ ## Usage
59
+
60
+ ### Browser
45
61
 
46
62
  ```typescript
47
- import {
48
- parseUA, // 纯函数,可注入环境上下文
49
- isWebview, // 检测 Android Webview
50
- isWechatMiniapp, // 检测微信小程序
51
- getLanguage, // 获取浏览器语言
52
- getWindowsVersion,// 异步获取 Windows 11/10 版本
53
- detectBot, // 单独使用爬虫检测
54
- detectArch, // 单独使用架构检测
55
- detectHeadless, // 单独使用无头浏览器检测
56
- VERSION // 当前版本号
57
- } from 'ua-browser'
63
+ import uaBrowser from 'ua-browser'
64
+
65
+ const { browser, os, device } = uaBrowser()
66
+
67
+ if (device === 'Mobile') {
68
+ // redirect to mobile version
69
+ }
70
+
71
+ if (browser === 'Wechat') {
72
+ // WeChat in-app browser logic
73
+ }
74
+ ```
75
+
76
+ ### Node.js / SSR
77
+
78
+ Use the pure `parseUA` function with the UA string from the request header:
79
+
80
+ ```typescript
81
+ import { parseUA } from 'ua-browser'
82
+
83
+ const ua = req.headers['user-agent'] ?? ''
84
+ const { browser, os, isBot } = parseUA(ua)
85
+
86
+ if (isBot) {
87
+ // block or allow crawlers
88
+ }
89
+ ```
90
+
91
+ ### CDN
92
+
93
+ ```html
94
+ <script src="https://cdn.jsdelivr.net/npm/ua-browser/dist/index.min.js"></script>
95
+ <script>
96
+ const { browser, os } = uaBrowser()
97
+ </script>
98
+ ```
99
+
100
+ ### Accurate Windows 10 / 11 Detection
101
+
102
+ Windows 10 and 11 share the same UA string. Use the Client Hints API to distinguish them:
103
+
104
+ ```typescript
105
+ import { parseUA, getWindowsVersion, getNavContext } from 'ua-browser'
106
+
107
+ const nav = getNavContext()
108
+ const windowsVersion = await getWindowsVersion(nav)
109
+ const result = parseUA(navigator.userAgent, { nav, windowsVersion })
110
+
111
+ console.log(result.osVersion) // '10' or '11'
58
112
  ```
59
113
 
60
114
  ## API
61
115
 
62
- ### `uaBrowser(ua?: string): EnvOption`
116
+ ### Default export `uaBrowser(ua?)`
117
+
118
+ Automatically injects the `navigator` context (language, platform, MIME types, etc.) in browser environments.
119
+
120
+ ```typescript
121
+ import uaBrowser from 'ua-browser'
122
+
123
+ uaBrowser() // reads navigator.userAgent automatically
124
+ uaBrowser(customUA) // custom UA string, still injects browser context
125
+ ```
126
+
127
+ ### Named exports (tree-shakeable)
128
+
129
+ ```typescript
130
+ import {
131
+ parseUA, // pure function, ideal for SSR / Node.js
132
+ getNavContext, // read current browser navigator context
133
+ getWindowsVersion, // async: accurately distinguish Windows 10 / 11
134
+ getLanguage, // extract browser language from NavContext
135
+ isWebview, // detect Android Webview (UA contains "; wv")
136
+ isWechatMiniapp, // detect WeChat Mini Program environment
137
+ detectBot, // standalone bot detection
138
+ detectArch, // standalone CPU architecture detection
139
+ detectHeadless, // standalone headless browser detection
140
+ VERSION, // current library version
141
+ } from 'ua-browser'
142
+ ```
63
143
 
64
- | 字段 | 类型 | 说明 |
144
+ ### Return value `EnvOption`
145
+
146
+ | Field | Type | Description |
65
147
  | :-- | :-- | :-- |
66
- | `browser` | `BrowserName` | 浏览器名称 |
67
- | `version` | `string` | 浏览器版本 |
68
- | `engine` | `EngineName` | 渲染内核 |
69
- | `os` | `OsName` | 操作系统 |
70
- | `osVersion` | `string` | 系统版本 |
71
- | `device` | `DeviceName` | 设备类型 |
72
- | `arch` | `ArchName` | CPU 架构 |
73
- | `isWebview` | `boolean` | 是否为 Android Webview |
74
- | `isHeadless` | `boolean` | 是否为无头/自动化浏览器 |
75
- | `isBot` | `boolean` | 是否为爬虫/机器人 |
76
- | `botName` | `BotName` | 爬虫名称 |
77
- | `language` | `string` | 浏览器语言 |
78
- | `platform` | `string` | 平台信息 |
148
+ | `browser` | `BrowserName` | Browser name |
149
+ | `version` | `string` | Browser version |
150
+ | `engine` | `EngineName` | Rendering engine |
151
+ | `os` | `OsName` | Operating system |
152
+ | `osVersion` | `string` | OS version |
153
+ | `device` | `DeviceName` | Device type: `Mobile` \| `Tablet` \| `TV` \| `PC` |
154
+ | `arch` | `ArchName` | CPU architecture |
155
+ | `isWebview` | `boolean` | Whether running in Android Webview |
156
+ | `isHeadless` | `boolean` | Whether running in a headless / automated browser |
157
+ | `isBot` | `boolean` | Whether the UA belongs to a bot / crawler |
158
+ | `botName` | `BotName` | Bot name |
159
+ | `language` | `string` | Browser language, e.g. `en-US` |
160
+ | `platform` | `string` | Platform identifier, e.g. `Win32` |
161
+
162
+ > All fields return `'unknown'` when undetected — never an empty string or `null`.
163
+
164
+ ## Supported
165
+
166
+ Over 60 browsers, 17 operating systems, and 19 bot rules built in. See the **[full support list](https://yangtianxia.github.io/ua-browser/guide/support-list)**.
167
+
168
+ Highlights:
169
+ - **Browsers** — Chrome, Safari, Firefox, Edge, Samsung Internet, UC, WeChat, DingTalk, TikTok and more
170
+ - **OS** — Windows, macOS, Android, iOS, HarmonyOS, Tizen, KaiOS and more
171
+ - **AI bots** — GPTBot, ClaudeBot, PerplexityBot, CCBot and more
172
+ - **Devices** — Mobile, Tablet, TV (Samsung Smart TV, HbbTV), PC
79
173
 
80
174
  ## License
81
175
 
82
- MIT
176
+ [MIT](./LICENSE) © yangtianxia
@@ -0,0 +1,176 @@
1
+ # ua-browser
2
+
3
+ [![npm version](https://img.shields.io/npm/v/ua-browser?color=cb3837)](https://www.npmjs.com/package/ua-browser)
4
+ [![npm downloads](https://img.shields.io/npm/dm/ua-browser)](https://www.npmjs.com/package/ua-browser)
5
+ [![license](https://img.shields.io/npm/l/ua-browser)](./LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ready-3178c6)](https://www.typescriptlang.org/)
7
+
8
+ 通过 User Agent 检测浏览器、操作系统、设备类型、渲染内核、CPU 架构、爬虫及无头浏览器。零依赖,支持浏览器与 Node.js 双环境。
9
+
10
+ **[📖 文档](https://yangtianxia.github.io/ua-browser/)** · **[🎮 Playground](https://yangtianxia.github.io/ua-browser/playground)** · **[English](./README.md)**
11
+
12
+ ## 特性
13
+
14
+ - **全面检测** — 浏览器、OS、渲染内核、设备类型(Mobile / Tablet / TV / PC)、CPU 架构、爬虫、无头浏览器
15
+ - **AI 爬虫识别** — 内置 GPTBot、ClaudeBot、PerplexityBot 等主流 AI 抓取机器人
16
+ - **零依赖** — 无任何运行时依赖,gzip 后体积极小
17
+ - **纯函数** — `parseUA()` 无全局状态,天然支持 SSR / Node.js
18
+ - **TypeScript** — 完整类型定义,`BrowserName`、`OsName` 等均为精确字面量联合类型
19
+ - **Tree-shakeable** — 所有功能按需导入,不引入多余代码
20
+
21
+ ## 安装
22
+
23
+ ```sh
24
+ npm i ua-browser
25
+ # pnpm
26
+ pnpm add ua-browser
27
+ # yarn
28
+ yarn add ua-browser
29
+ ```
30
+
31
+ ## 快速上手
32
+
33
+ ```typescript
34
+ import uaBrowser from 'ua-browser'
35
+
36
+ const info = uaBrowser()
37
+
38
+ console.log(info)
39
+ // {
40
+ // browser: 'Chrome',
41
+ // version: '124.0.0.0',
42
+ // engine: 'Blink',
43
+ // os: 'Windows',
44
+ // osVersion: '10',
45
+ // device: 'PC',
46
+ // arch: 'x86_64',
47
+ // isWebview: false,
48
+ // isHeadless: false,
49
+ // isBot: false,
50
+ // botName: 'unknown',
51
+ // language: 'zh-CN',
52
+ // platform: 'Win32'
53
+ // }
54
+ ```
55
+
56
+ > 传入自定义 UA 字符串:`uaBrowser('Mozilla/5.0 ...')`
57
+
58
+ ## 使用
59
+
60
+ ### 浏览器环境
61
+
62
+ ```typescript
63
+ import uaBrowser from 'ua-browser'
64
+
65
+ const { browser, os, device } = uaBrowser()
66
+
67
+ if (device === 'Mobile') {
68
+ // 跳转移动版
69
+ }
70
+
71
+ if (browser === 'Wechat') {
72
+ // 微信内置浏览器逻辑
73
+ }
74
+ ```
75
+
76
+ ### Node.js / SSR
77
+
78
+ 使用 `parseUA` 纯函数,传入请求头中的 UA 字符串:
79
+
80
+ ```typescript
81
+ import { parseUA } from 'ua-browser'
82
+
83
+ const ua = req.headers['user-agent'] ?? ''
84
+ const { browser, os, isBot } = parseUA(ua)
85
+
86
+ if (isBot) {
87
+ // 拦截或放行爬虫
88
+ }
89
+ ```
90
+
91
+ ### CDN
92
+
93
+ ```html
94
+ <script src="https://cdn.jsdelivr.net/npm/ua-browser/dist/index.min.js"></script>
95
+ <script>
96
+ const { browser, os } = uaBrowser()
97
+ </script>
98
+ ```
99
+
100
+ ### 精确区分 Windows 10 / 11
101
+
102
+ Windows 10 和 11 的 UA 字符串相同,需借助 Client Hints API 异步获取:
103
+
104
+ ```typescript
105
+ import { parseUA, getWindowsVersion, getNavContext } from 'ua-browser'
106
+
107
+ const nav = getNavContext()
108
+ const windowsVersion = await getWindowsVersion(nav)
109
+ const result = parseUA(navigator.userAgent, { nav, windowsVersion })
110
+
111
+ console.log(result.osVersion) // '10' 或 '11'
112
+ ```
113
+
114
+ ## API
115
+
116
+ ### 默认导出 `uaBrowser(ua?)`
117
+
118
+ 在浏览器环境中自动注入 `navigator` 上下文(语言、平台、MIME 类型等)。
119
+
120
+ ```typescript
121
+ import uaBrowser from 'ua-browser'
122
+
123
+ uaBrowser() // 自动读取 navigator.userAgent
124
+ uaBrowser(customUA) // 传入自定义 UA,仍注入当前浏览器上下文
125
+ ```
126
+
127
+ ### 命名导出(按需引入)
128
+
129
+ ```typescript
130
+ import {
131
+ parseUA, // 纯函数,适合 SSR / Node.js
132
+ getNavContext, // 读取当前浏览器 navigator 上下文
133
+ getWindowsVersion, // 异步精确区分 Windows 10 / 11
134
+ getLanguage, // 从 NavContext 获取浏览器语言
135
+ isWebview, // 检测 Android Webview(UA 含 "; wv")
136
+ isWechatMiniapp, // 检测微信小程序运行环境
137
+ detectBot, // 独立爬虫检测
138
+ detectArch, // 独立 CPU 架构检测
139
+ detectHeadless, // 独立无头浏览器检测
140
+ VERSION, // 当前版本号
141
+ } from 'ua-browser'
142
+ ```
143
+
144
+ ### 返回值 `EnvOption`
145
+
146
+ | 字段 | 类型 | 说明 |
147
+ | :-- | :-- | :-- |
148
+ | `browser` | `BrowserName` | 浏览器名称 |
149
+ | `version` | `string` | 浏览器版本 |
150
+ | `engine` | `EngineName` | 渲染内核 |
151
+ | `os` | `OsName` | 操作系统 |
152
+ | `osVersion` | `string` | 系统版本 |
153
+ | `device` | `DeviceName` | 设备类型:`Mobile` \| `Tablet` \| `TV` \| `PC` |
154
+ | `arch` | `ArchName` | CPU 架构 |
155
+ | `isWebview` | `boolean` | 是否为 Android Webview |
156
+ | `isHeadless` | `boolean` | 是否为无头 / 自动化浏览器 |
157
+ | `isBot` | `boolean` | 是否为爬虫 / 机器人 |
158
+ | `botName` | `BotName` | 爬虫名称 |
159
+ | `language` | `string` | 浏览器语言,如 `zh-CN` |
160
+ | `platform` | `string` | 平台标识,如 `Win32` |
161
+
162
+ > 所有字段在无法识别时统一返回 `'unknown'`,不返回空字符串或 `null`。
163
+
164
+ ## 支持范围
165
+
166
+ 内置超过 60 种浏览器、17 种操作系统、19 种爬虫规则,详见 **[内置支持列表](https://yangtianxia.github.io/ua-browser/guide/support-list)**。
167
+
168
+ 部分覆盖:
169
+ - **浏览器** — Chrome、Safari、Firefox、Edge、Samsung Internet、UC、微信、钉钉、抖音等
170
+ - **操作系统** — Windows、macOS、Android、iOS、HarmonyOS、Tizen、KaiOS 等
171
+ - **AI 爬虫** — GPTBot、ClaudeBot、PerplexityBot、CCBot 等
172
+ - **设备** — Mobile、Tablet、TV(含三星 Smart TV、HbbTV 标准)、PC
173
+
174
+ ## License
175
+
176
+ [MIT](./LICENSE) © yangtianxia
package/dist/index.cjs CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  // package.json
6
6
  var package_default = {
7
- version: "1.0.0"};
7
+ version: "1.0.2"};
8
8
 
9
9
  // src/constants/browsers.ts
10
10
  var BROWSER_DEFS = [
@@ -19,6 +19,9 @@ var BROWSER_DEFS = [
19
19
  { name: "Opera", priority: 70, detect: /(Opera|OPR|OPT)/, versionPattern: [/Opera\/([\d.]+)/, /OPR\/([\d.]+)/, /OPT\/([\d.]+)/] },
20
20
  { name: "Vivaldi", priority: 80, detect: /Vivaldi/, versionPattern: /Vivaldi\/([\d.]+)/ },
21
21
  { name: "Yandex", priority: 90, detect: /YaBrowser/, versionPattern: /YaBrowser\/([\d.]+)/ },
22
+ { name: "Samsung Internet", priority: 92, detect: /SamsungBrowser/, versionPattern: /SamsungBrowser\/([\d.]+)/ },
23
+ { name: "DuckDuckGo", priority: 94, detect: /DuckDuckGo\//, versionPattern: /DuckDuckGo\/([\d.]+)/ },
24
+ { name: "Puffin", priority: 96, detect: /Puffin\//, versionPattern: /Puffin\/([\d.]+)/ },
22
25
  { name: "Arora", priority: 100, detect: /Arora/, versionPattern: /Arora\/([\d.]+)/ },
23
26
  { name: "Lunascape", priority: 110, detect: /Lunascape/, versionPattern: /Lunascape[\s]([\d.]+)/ },
24
27
  { name: "QupZilla", priority: 120, detect: /QupZilla/, versionPattern: /QupZilla[\s]([\d.]+)/ },
@@ -194,21 +197,28 @@ var OS_DEFS = [
194
197
  { name: "Symbian", detect: /Symbian/, versionPattern: null },
195
198
  { name: "MeeGo", detect: /MeeGo/, versionPattern: null },
196
199
  { name: "BlackBerry", detect: /(BlackBerry|RIM)/, versionPattern: null },
197
- { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ },
198
200
  { name: "FreeBSD", detect: /FreeBSD/, versionPattern: null },
199
201
  { name: "Debian", detect: /Debian/, versionPattern: /Debian\/([\d.]+)/ },
200
202
  { name: "Ubuntu", detect: /Ubuntu/, versionPattern: null },
201
- { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
203
+ // Linux must come before Chrome OS: Chrome OS UAs contain "X11", so Linux matches first,
204
+ // then Chrome OS overrides it.
202
205
  { name: "Linux", detect: /(Linux|X11)/, versionPattern: null },
206
+ { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
207
+ { name: "Tizen", detect: /Tizen/, versionPattern: /Tizen ([\d.]+)/ },
203
208
  { name: "iOS", detect: /like Mac OS X/, versionPattern: /OS ([\d_]+) like/ },
204
209
  { name: "MacOS", detect: /Macintosh/, versionPattern: /Mac OS X -?([\d_.]+)/ },
210
+ { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
211
+ // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
212
+ // first, then HarmonyOS overrides it.
205
213
  {
206
214
  name: "HarmonyOS",
207
215
  detect: /HarmonyOS/,
208
216
  versionPattern: /Android ([\d.]+)[;)]/,
209
217
  versionLookup: { "10": "2" }
210
218
  },
211
- { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
219
+ { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
220
+ // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
221
+ // matches first, then Windows Phone overrides it.
212
222
  {
213
223
  name: "Windows",
214
224
  detect: /Windows/,
@@ -224,7 +234,8 @@ var OS_DEFS = [
224
234
  "5.1": "XP",
225
235
  "5.0": "2000"
226
236
  }
227
- }
237
+ },
238
+ { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
228
239
  ];
229
240
 
230
241
  // src/detectors/os.ts
@@ -246,7 +257,8 @@ function detectOs(ua, windowsVersion) {
246
257
  if (mapped !== null) {
247
258
  osVersion = mapped;
248
259
  } else if (matchedDef.name === "Windows") {
249
- osVersion = parseInt(normalised, 10).toString();
260
+ const n = parseInt(normalised, 10);
261
+ osVersion = isNaN(n) ? "unknown" : n.toString();
250
262
  }
251
263
  } else {
252
264
  osVersion = normalised;
@@ -261,18 +273,24 @@ function detectOs(ua, windowsVersion) {
261
273
 
262
274
  // src/constants/devices.ts
263
275
  var DEVICE_DEFS = [
264
- { name: "Mobile", detect: /(Mobi|iPh|480)/ },
265
- { name: "Tablet", detect: /(Tablet|Pad|Nexus 7)/ }
276
+ { name: "Mobile", detect: /(Mobi|iPh)/ },
277
+ { name: "Tablet", detect: /(Tablet|Pad)/ }
266
278
  ];
267
279
 
268
280
  // src/detectors/device.ts
269
281
  function detectDevice(ua, nav) {
282
+ if (/(SMART-TV|HbbTV|SmartTV|TV Safari|Android TV|GoogleTV)/.test(ua)) {
283
+ return "TV";
284
+ }
270
285
  if ((nav == null ? void 0 : nav.platform) === "MacIntel" && nav.maxTouchPoints > 1) {
271
286
  return "Tablet";
272
287
  }
273
288
  if (/iPad/.test(ua)) {
274
289
  return "Tablet";
275
290
  }
291
+ if (/Android/.test(ua) && !/Mobile/.test(ua)) {
292
+ return "Tablet";
293
+ }
276
294
  for (const def of DEVICE_DEFS) {
277
295
  if (def.detect.test(ua)) {
278
296
  return def.name;
@@ -303,6 +321,12 @@ var BOT_DEFS = [
303
321
  { name: "SemrushBot", detect: /SemrushBot/ },
304
322
  { name: "AhrefsBot", detect: /AhrefsBot/ },
305
323
  { name: "MJ12bot", detect: /MJ12bot/ },
324
+ // AI / LLM crawlers
325
+ { name: "GPTBot", detect: /GPTBot/ },
326
+ { name: "ClaudeBot", detect: /ClaudeBot/ },
327
+ { name: "PerplexityBot", detect: /PerplexityBot/ },
328
+ { name: "CCBot", detect: /CCBot/ },
329
+ { name: "AdsBot", detect: /AdsBot-Google/ },
306
330
  // Generic catch-all (must be last)
307
331
  { name: "GenericBot", detect: /(bot|crawler|spider|crawling|scraper)/i }
308
332
  ];
@@ -336,7 +360,7 @@ function detectArch(ua) {
336
360
  }
337
361
 
338
362
  // src/detectors/headless.ts
339
- var HEADLESS_PATTERN = /(HeadlessChrome|Headless|PhantomJS|Electron\/|Playwright)/;
363
+ var HEADLESS_PATTERN = /(HeadlessChrome|Headless|PhantomJS|Electron\/|Playwright|jsdom\/|Selenium)/;
340
364
  function detectHeadless(ua) {
341
365
  return HEADLESS_PATTERN.test(ua);
342
366
  }
@@ -365,10 +389,11 @@ function getLanguage(nav) {
365
389
 
366
390
  // src/parse.ts
367
391
  function parseUA(ua, options = {}) {
368
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
392
+ var _a, _b, _c, _d, _e;
369
393
  const { nav, windowsVersion } = options;
370
394
  const { browser: rawBrowser, version: rawVersion } = detectBrowser(ua);
371
- const { os, osVersion } = detectOs(ua, windowsVersion);
395
+ const { os, osVersion: rawOsVersion } = detectOs(ua, windowsVersion);
396
+ let osVersion = rawOsVersion;
372
397
  const device = detectDevice(ua, nav);
373
398
  const arch = detectArch(ua);
374
399
  const { isBot, botName } = detectBot(ua);
@@ -379,7 +404,8 @@ function parseUA(ua, options = {}) {
379
404
  let version = rawVersion;
380
405
  if (nav) {
381
406
  const chromeGlobal = typeof chrome !== "undefined" ? chrome : void 0;
382
- const chromeMajor = parseInt((_c = ((_b = /Chrome\/([\d]+)/.exec(ua)) != null ? _b : [])[1]) != null ? _c : "0", 10);
407
+ const chromeMatch = /Chrome\/([\d]+)/.exec(ua);
408
+ const chromeMajor = chromeMatch ? parseInt(chromeMatch[1], 10) : 0;
383
409
  if (chromeGlobal) {
384
410
  if (chromeGlobal.adblock2345 || chromeGlobal.common2345) {
385
411
  browser = "2345Explorer";
@@ -395,7 +421,8 @@ function parseUA(ua, options = {}) {
395
421
  }
396
422
  }
397
423
  if (is360) {
398
- if (getMimeType(nav, "application/gameplugin") || !((_d = nav.connection) == null ? void 0 : _d.saveData)) {
424
+ const saveDataEnabled = ((_b = nav.connection) == null ? void 0 : _b.saveData) === true;
425
+ if (getMimeType(nav, "application/gameplugin") || !saveDataEnabled) {
399
426
  browser = "360SE";
400
427
  } else {
401
428
  browser = "360EE";
@@ -407,7 +434,8 @@ function parseUA(ua, options = {}) {
407
434
  }
408
435
  if (browser === "Baidu" && /(Opera|OPR|OPT)/.test(ua)) {
409
436
  browser = "Opera";
410
- version = (_j = (_i = (_g = ((_e = /OPR\/([\d.]+)/.exec(ua)) != null ? _e : [])[1]) != null ? _g : ((_f = /OPT\/([\d.]+)/.exec(ua)) != null ? _f : [])[1]) != null ? _i : ((_h = /Opera\/([\d.]+)/.exec(ua)) != null ? _h : [])[1]) != null ? _j : "unknown";
437
+ const opVer = (_d = (_c = /OPR\/([\d.]+)/.exec(ua)) != null ? _c : /OPT\/([\d.]+)/.exec(ua)) != null ? _d : /Opera\/([\d.]+)/.exec(ua);
438
+ version = (_e = opVer == null ? void 0 : opVer[1]) != null ? _e : "unknown";
411
439
  }
412
440
  if (browser === "Chrome" && /\S+Browser\//.test(ua)) {
413
441
  const m = /(\S+Browser)\/([\d.]+)/.exec(ua);
@@ -432,6 +460,12 @@ function parseUA(ua, options = {}) {
432
460
  } catch (e) {
433
461
  }
434
462
  }
463
+ if (os === "iOS" && browser === "Safari") {
464
+ const m = /Version\/([\d.]+)/.exec(ua);
465
+ if (m && parseInt(m[1], 10) > parseInt(osVersion, 10)) {
466
+ osVersion = m[1];
467
+ }
468
+ }
435
469
  const engine = detectEngine(ua, browser, version);
436
470
  return {
437
471
  browser,
@@ -452,13 +486,13 @@ function parseUA(ua, options = {}) {
452
486
 
453
487
  // src/utils/windows-version.ts
454
488
  async function getWindowsVersion(nav) {
455
- var _a, _b;
489
+ var _a;
456
490
  if (!nav.userAgentData || nav.userAgentData.platform !== "Windows") {
457
491
  return null;
458
492
  }
459
493
  try {
460
494
  const data = await nav.userAgentData.getHighEntropyValues(["platformVersion"]);
461
- const major = parseInt((_b = (_a = data["platformVersion"]) == null ? void 0 : _a.split(".")[0]) != null ? _b : "0", 10);
495
+ const major = parseInt(((_a = data["platformVersion"]) == null ? void 0 : _a.split(".")[0]) || "0", 10);
462
496
  return major >= 13 ? "11" : "10";
463
497
  } catch (e) {
464
498
  return null;