ua-browser 1.4.0-beta.0 → 1.4.0

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
@@ -182,6 +182,8 @@ import {
182
182
  detectBot, // standalone bot detection
183
183
  detectBrowser, // standalone browser detection
184
184
  detectOS, // standalone OS detection
185
+ detectEngine, // standalone rendering engine detection
186
+ detectDevice, // standalone device type detection
185
187
  detectArch, // standalone CPU architecture detection
186
188
  detectHeadless, // standalone headless browser detection
187
189
  satisfies, // condition-matching helper
package/README.md CHANGED
@@ -164,6 +164,8 @@ import {
164
164
  detectBot, // 独立爬虫检测
165
165
  detectBrowser, // 独立浏览器检测
166
166
  detectOS, // 独立操作系统检测
167
+ detectEngine, // 独立渲染引擎检测
168
+ detectDevice, // 独立设备类型检测
167
169
  detectArch, // 独立 CPU 架构检测
168
170
  detectHeadless, // 独立无头浏览器检测
169
171
  satisfies, // 条件匹配辅助函数
package/dist/index.cjs CHANGED
@@ -4,7 +4,86 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  // package.json
6
6
  var package_default = {
7
- version: "1.4.0-beta.0"};
7
+ version: "1.4.0"};
8
+
9
+ // src/constants/os.ts
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/ },
24
+ {
25
+ name: "MacOS",
26
+ detect: /Macintosh/,
27
+ versionPattern: /Mac OS X -?([\d_.]+)/,
28
+ versionNames: {
29
+ "10.9": "Mavericks",
30
+ "10.10": "Yosemite",
31
+ "10.11": "El Capitan",
32
+ "10.12": "Sierra",
33
+ "10.13": "High Sierra",
34
+ "10.14": "Mojave",
35
+ "10.15": "Catalina",
36
+ "11": "Big Sur",
37
+ "12": "Monterey",
38
+ "13": "Ventura",
39
+ "14": "Sonoma",
40
+ "15": "Sequoia"
41
+ }
42
+ },
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.
51
+ {
52
+ name: "HarmonyOS",
53
+ detect: /HarmonyOS/,
54
+ versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
55
+ versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
56
+ },
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.
62
+ {
63
+ name: "Windows",
64
+ detect: /Windows/,
65
+ versionPattern: /Windows NT ([\d.]+)/,
66
+ versionLookup: {
67
+ "10": "10",
68
+ "6.4": "10",
69
+ "6.3": "8.1",
70
+ "6.2": "8",
71
+ "6.1": "7",
72
+ "6.0": "Vista",
73
+ "5.2": "XP",
74
+ "5.1": "XP",
75
+ "5.0": "2000"
76
+ },
77
+ versionNames: {
78
+ "7": "Windows 7",
79
+ "8": "Windows 8",
80
+ "8.1": "Windows 8.1",
81
+ "10": "Windows 10",
82
+ "11": "Windows 11"
83
+ }
84
+ },
85
+ { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
86
+ ];
8
87
 
9
88
  // src/constants/browsers.ts
10
89
  var BROWSER_DEFS = [
@@ -151,7 +230,7 @@ function detectBrowser(ua) {
151
230
  }
152
231
  }
153
232
  }
154
- if (!best) return { browser: "unknown", version: "unknown" };
233
+ if (!best) return { browser: "unknown", version: "unknown", browserType: "unknown", priority: 0 };
155
234
  let version = null;
156
235
  if (best.chromeLookup) {
157
236
  version = lookupFromChromeVersion(ua, best.chromeLookup);
@@ -160,7 +239,9 @@ function detectBrowser(ua) {
160
239
  const patterns = Array.isArray(best.versionPattern) ? best.versionPattern : [best.versionPattern];
161
240
  version = extractVersionFromPatterns(ua, patterns);
162
241
  }
163
- return { browser: best.name, version: version != null ? version : "unknown" };
242
+ const p = best.priority;
243
+ const browserType = p >= 500 ? "app" : p >= 300 ? "brand" : "browser";
244
+ return { browser: best.name, version: version != null ? version : "unknown", browserType, priority: p };
164
245
  }
165
246
 
166
247
  // src/constants/engines.ts
@@ -176,8 +257,22 @@ var ENGINE_DEFS = [
176
257
  ];
177
258
 
178
259
  // src/detectors/engine.ts
260
+ var ENGINE_VERSION_RE = {
261
+ WebKit: /AppleWebKit\/([\d.]+)/,
262
+ Blink: /AppleWebKit\/([\d.]+)/,
263
+ ArkWeb: /AppleWebKit\/([\d.]+)/,
264
+ Gecko: /Gecko\/([\d.]+)/,
265
+ Trident: /Trident\/([\d.]+)/,
266
+ Presto: /Presto\/([\d.]+)/,
267
+ KHTML: /KHTML\/([\d.]+)/
268
+ };
179
269
  function detectEngine(ua, browser, version) {
180
- var _a, _b;
270
+ var _a, _b, _c, _d;
271
+ if (browser === void 0 || version === void 0) {
272
+ const b = detectBrowser(ua);
273
+ browser = browser != null ? browser : b.browser;
274
+ version = version != null ? version : b.version;
275
+ }
181
276
  let engine = "unknown";
182
277
  for (const def of ENGINE_DEFS) {
183
278
  if (def.detect.test(ua)) {
@@ -194,64 +289,19 @@ function detectEngine(ua, browser, version) {
194
289
  if (browser === "Edge") {
195
290
  engine = parseInt(version, 10) > 75 ? "Blink" : "EdgeHTML";
196
291
  }
197
- return engine;
292
+ const engineVersion = ENGINE_VERSION_RE[engine] ? (_d = (_c = ENGINE_VERSION_RE[engine].exec(ua)) == null ? void 0 : _c[1]) != null ? _d : "unknown" : "unknown";
293
+ return { engine, engineVersion };
198
294
  }
199
295
 
200
- // src/constants/os.ts
201
- var OS_DEFS = [
202
- { name: "WebOS", detect: /hpwOS/, versionPattern: /hpwOS\/([\d.]+)/ },
203
- { name: "Symbian", detect: /Symbian/, versionPattern: null },
204
- { name: "MeeGo", detect: /MeeGo/, versionPattern: null },
205
- { name: "BlackBerry", detect: /(BlackBerry|RIM)/, versionPattern: null },
206
- { name: "FreeBSD", detect: /FreeBSD/, versionPattern: null },
207
- { name: "Debian", detect: /Debian/, versionPattern: /Debian\/([\d.]+)/ },
208
- { name: "Ubuntu", detect: /Ubuntu/, versionPattern: null },
209
- // Linux must come before Chrome OS: Chrome OS UAs contain "X11", so Linux matches first,
210
- // then Chrome OS overrides it.
211
- { name: "Linux", detect: /(Linux|X11)/, versionPattern: null },
212
- { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
213
- { name: "Tizen", detect: /Tizen/, versionPattern: /Tizen ([\d.]+)/ },
214
- { name: "iOS", detect: /like Mac OS X/, versionPattern: /OS ([\d_]+) like/ },
215
- { name: "MacOS", detect: /Macintosh/, versionPattern: /Mac OS X -?([\d_.]+)/ },
216
- // visionOS / tvOS must come AFTER iOS: their UAs also contain "like Mac OS X",
217
- // so they need to override iOS via the last-match-wins iteration.
218
- { name: "visionOS", detect: /visionOS/, versionPattern: /visionOS ([\d_]+)/ },
219
- { name: "tvOS", detect: /Apple TV/, versionPattern: /OS ([\d_]+) like/ },
220
- { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
221
- // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
222
- // first, then HarmonyOS overrides it. versionPattern tries direct extraction first (5.0+
223
- // pure HarmonyOS UAs don't have Android token), then falls back to Android version + lookup.
224
- {
225
- name: "HarmonyOS",
226
- detect: /HarmonyOS/,
227
- versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
228
- versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
229
- },
230
- // OpenHarmony (open-source base) must come after HarmonyOS to override any earlier match.
231
- { name: "OpenHarmony", detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
232
- { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
233
- // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
234
- // matches first, then Windows Phone overrides it.
235
- {
236
- name: "Windows",
237
- detect: /Windows/,
238
- versionPattern: /Windows NT ([\d.]+)/,
239
- versionLookup: {
240
- "10": "10",
241
- "6.4": "10",
242
- "6.3": "8.1",
243
- "6.2": "8",
244
- "6.1": "7",
245
- "6.0": "Vista",
246
- "5.2": "XP",
247
- "5.1": "XP",
248
- "5.0": "2000"
249
- }
250
- },
251
- { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
252
- ];
253
-
254
296
  // src/detectors/os.ts
297
+ function lookupVersionName(map, version) {
298
+ const parts = version.split(".");
299
+ for (let len = parts.length; len >= 1; len--) {
300
+ const key = parts.slice(0, len).join(".");
301
+ if (Object.prototype.hasOwnProperty.call(map, key)) return map[key];
302
+ }
303
+ return "unknown";
304
+ }
255
305
  function detectOs(ua, windowsVersion) {
256
306
  let matchedDef = null;
257
307
  for (const def of OS_DEFS) {
@@ -259,7 +309,7 @@ function detectOs(ua, windowsVersion) {
259
309
  matchedDef = def;
260
310
  }
261
311
  }
262
- if (!matchedDef) return { os: "unknown", osVersion: "unknown" };
312
+ if (!matchedDef) return { os: "unknown", osVersion: "unknown", osVersionName: "unknown" };
263
313
  let osVersion = "unknown";
264
314
  if (matchedDef.versionPattern) {
265
315
  const raw = Array.isArray(matchedDef.versionPattern) ? extractVersionFromPatterns(ua, matchedDef.versionPattern) : extractVersion(ua, matchedDef.versionPattern);
@@ -283,7 +333,8 @@ function detectOs(ua, windowsVersion) {
283
333
  if (matchedDef.name === "Windows" && windowsVersion) {
284
334
  osVersion = windowsVersion;
285
335
  }
286
- return { os: matchedDef.name, osVersion };
336
+ const osVersionName = matchedDef.versionNames ? lookupVersionName(matchedDef.versionNames, osVersion) : "unknown";
337
+ return { os: matchedDef.name, osVersion, osVersionName };
287
338
  }
288
339
 
289
340
  // src/constants/devices.ts
@@ -348,57 +399,93 @@ function detectDevice(ua, nav) {
348
399
  }
349
400
  return (_b = (_a = hwDetect()) != null ? _a : uaDetect()) != null ? _b : "PC";
350
401
  }
402
+ var VENDOR_MAP = [
403
+ [/^SM-|^GT-|^SGH-|^SCH-|^SHW-|^SPH-|^SAMSUNG /i, "Samsung"],
404
+ [/^Pixel |^Nexus /, "Google"],
405
+ [/^moto |^motorola /i, "Motorola"],
406
+ [/^HONOR /i, "Honor"],
407
+ [/^ELS-|^LYA-|^HMA-|^VOG-|^ANA-|^CLT-|^JNY-|^NOH-|^PLK-|^BAH-|^BKL-|^COL-|^DUB-|^FIG-|^KIW-|^MAR-|^VTR-|^WAS-/i, "Huawei"],
408
+ [/^CPH/, "OPPO"],
409
+ [/^RMX/, "Realme"],
410
+ [/^V\d{4}[A-Z]|^vivo /i, "Vivo"],
411
+ [/^iqoo/i, "Vivo"],
412
+ [/^Redmi |^POCO |^Mi /, "Xiaomi"],
413
+ [/^2\d{9}|^2\d{3}[A-Z]/, "Xiaomi"],
414
+ [/^OnePlus |^IN2|^KB2|^LE2/i, "OnePlus"],
415
+ [/^LM-|^LGE |^LG-/i, "LG"],
416
+ [/^HTC/i, "HTC"],
417
+ [/^Nokia/i, "Nokia"],
418
+ [/^XQ-/, "Sony"],
419
+ [/^ASUS_|^ZB6|^ZS6|^ZE[56]/i, "Asus"],
420
+ [/^Quest /i, "Meta"]
421
+ ];
422
+ var ANDROID_MODEL_RE = /Android [0-9.]+;(?:\s*U;)?(?:\s*[a-z]{2}[-_][a-zA-Z]{2,4};)?\s*([^;)]+?)(?:\s+Build\/|[;)])/;
423
+ function detectVendorModel(ua) {
424
+ if (/visionOS/.test(ua)) return { vendor: "Apple", model: "Apple Vision Pro" };
425
+ if (/iPhone/.test(ua)) return { vendor: "Apple", model: "iPhone" };
426
+ if (/iPad/.test(ua)) return { vendor: "Apple", model: "iPad" };
427
+ if (/iPod/.test(ua)) return { vendor: "Apple", model: "iPod touch" };
428
+ const m = ANDROID_MODEL_RE.exec(ua);
429
+ if (m) {
430
+ const model = m[1].trim();
431
+ for (const [re, vendor] of VENDOR_MAP) {
432
+ if (re.test(model)) return { vendor, model };
433
+ }
434
+ return { vendor: "unknown", model };
435
+ }
436
+ return { vendor: "unknown", model: "unknown" };
437
+ }
351
438
 
352
439
  // src/constants/bots.ts
353
440
  var BOT_DEFS = [
354
441
  // Search engines
355
- { name: "Googlebot", detect: /Googlebot/ },
356
- { name: "Bingbot", detect: /(bingbot|BingPreview)/ },
357
- { name: "Baiduspider", detect: /(Baiduspider|BaiduMobaider)/ },
358
- { name: "Bytespider", detect: /Bytespider/ },
359
- { name: "YandexBot", detect: /YandexBot/ },
360
- { name: "DuckDuckBot", detect: /DuckDuckBot/ },
361
- { name: "Slurp", detect: /Slurp/ },
362
- { name: "Sogou", detect: /(Sogou|sogou).*[Ss]pider/ },
363
- { name: "360Spider", detect: /360Spider/ },
364
- { name: "PetalBot", detect: /PetalBot/ },
365
- { name: "Applebot-Extended", detect: /Applebot-Extended/ },
366
- { name: "Applebot", detect: /Applebot/ },
442
+ { name: "Googlebot", detect: /Googlebot/, category: "search-engine" },
443
+ { name: "Bingbot", detect: /(bingbot|BingPreview)/, category: "search-engine" },
444
+ { name: "Baiduspider", detect: /(Baiduspider|BaiduMobaider)/, category: "search-engine" },
445
+ { name: "Bytespider", detect: /Bytespider/, category: "search-engine" },
446
+ { name: "YandexBot", detect: /YandexBot/, category: "search-engine" },
447
+ { name: "DuckDuckBot", detect: /DuckDuckBot/, category: "search-engine" },
448
+ { name: "Slurp", detect: /Slurp/, category: "search-engine" },
449
+ { name: "Sogou", detect: /(Sogou|sogou).*[Ss]pider/, category: "search-engine" },
450
+ { name: "360Spider", detect: /360Spider/, category: "search-engine" },
451
+ { name: "PetalBot", detect: /PetalBot/, category: "search-engine" },
452
+ { name: "Applebot-Extended", detect: /Applebot-Extended/, category: "search-engine" },
453
+ { name: "Applebot", detect: /Applebot/, category: "search-engine" },
367
454
  // Social media crawlers
368
- { name: "Facebookbot", detect: /(facebookexternalhit|FacebookBot)/ },
369
- { name: "Twitterbot", detect: /Twitterbot/ },
370
- { name: "LinkedInBot", detect: /LinkedInBot/ },
371
- { name: "PinterestBot", detect: /Pinterest/ },
455
+ { name: "Facebookbot", detect: /(facebookexternalhit|FacebookBot)/, category: "social" },
456
+ { name: "Twitterbot", detect: /Twitterbot/, category: "social" },
457
+ { name: "LinkedInBot", detect: /LinkedInBot/, category: "social" },
458
+ { name: "PinterestBot", detect: /Pinterest/, category: "social" },
372
459
  // Messaging link preview bots
373
- { name: "Slackbot", detect: /Slackbot/ },
374
- { name: "Discordbot", detect: /Discordbot/ },
375
- { name: "TelegramBot", detect: /TelegramBot/ },
376
- { name: "WhatsApp", detect: /WhatsApp/ },
460
+ { name: "Slackbot", detect: /Slackbot/, category: "link-preview" },
461
+ { name: "Discordbot", detect: /Discordbot/, category: "link-preview" },
462
+ { name: "TelegramBot", detect: /TelegramBot/, category: "link-preview" },
463
+ { name: "WhatsApp", detect: /WhatsApp/, category: "link-preview" },
377
464
  // SEO tools
378
- { name: "SemrushBot", detect: /SemrushBot/ },
379
- { name: "AhrefsBot", detect: /AhrefsBot/ },
380
- { name: "MJ12bot", detect: /MJ12bot/ },
381
- { name: "ScreamingFrog", detect: /Screaming Frog/ },
382
- { name: "DataForSeoBot", detect: /DataForSeoBot/ },
465
+ { name: "SemrushBot", detect: /SemrushBot/, category: "seo-tool" },
466
+ { name: "AhrefsBot", detect: /AhrefsBot/, category: "seo-tool" },
467
+ { name: "MJ12bot", detect: /MJ12bot/, category: "seo-tool" },
468
+ { name: "ScreamingFrog", detect: /Screaming Frog/, category: "seo-tool" },
469
+ { name: "DataForSeoBot", detect: /DataForSeoBot/, category: "seo-tool" },
383
470
  // AI / LLM crawlers
384
- { name: "GPTBot", detect: /GPTBot/ },
385
- { name: "OAI-SearchBot", detect: /OAI-SearchBot/ },
386
- { name: "ChatGPT-User", detect: /ChatGPT-User/ },
387
- { name: "ClaudeBot", detect: /ClaudeBot/ },
388
- { name: "PerplexityBot", detect: /PerplexityBot/ },
389
- { name: "CCBot", detect: /CCBot/ },
390
- { name: "AdsBot", detect: /AdsBot-Google/ },
391
- { name: "Google-Extended", detect: /Google-Extended/ },
392
- { name: "Meta-ExternalAgent", detect: /meta-externalagent/i },
393
- { name: "Amazonbot", detect: /Amazonbot/ },
394
- { name: "Diffbot", detect: /Diffbot/ },
395
- { name: "cohere-ai", detect: /cohere-ai/ },
396
- { name: "YouBot", detect: /YouBot/ },
471
+ { name: "GPTBot", detect: /GPTBot/, category: "ai-llm" },
472
+ { name: "OAI-SearchBot", detect: /OAI-SearchBot/, category: "ai-llm" },
473
+ { name: "ChatGPT-User", detect: /ChatGPT-User/, category: "ai-llm" },
474
+ { name: "ClaudeBot", detect: /ClaudeBot/, category: "ai-llm" },
475
+ { name: "PerplexityBot", detect: /PerplexityBot/, category: "ai-llm" },
476
+ { name: "CCBot", detect: /CCBot/, category: "ai-llm" },
477
+ { name: "AdsBot", detect: /AdsBot-Google/, category: "ai-llm" },
478
+ { name: "Google-Extended", detect: /Google-Extended/, category: "ai-llm" },
479
+ { name: "Meta-ExternalAgent", detect: /meta-externalagent/i, category: "ai-llm" },
480
+ { name: "Amazonbot", detect: /Amazonbot/, category: "ai-llm" },
481
+ { name: "Diffbot", detect: /Diffbot/, category: "ai-llm" },
482
+ { name: "cohere-ai", detect: /cohere-ai/, category: "ai-llm" },
483
+ { name: "YouBot", detect: /YouBot/, category: "ai-llm" },
397
484
  // Monitoring / archiving
398
- { name: "UptimeRobot", detect: /UptimeRobot/ },
399
- { name: "ia_archiver", detect: /ia_archiver/ },
485
+ { name: "UptimeRobot", detect: /UptimeRobot/, category: "monitoring" },
486
+ { name: "ia_archiver", detect: /ia_archiver/, category: "monitoring" },
400
487
  // Generic catch-all (must be last)
401
- { name: "GenericBot", detect: /(bot|crawler|spider|crawling|scraper)/i }
488
+ { name: "GenericBot", detect: /(bot|crawler|spider|crawling|scraper)/i, category: "generic" }
402
489
  ];
403
490
 
404
491
  // src/detectors/bot.ts
@@ -406,10 +493,10 @@ function detectBot(ua, customDefs) {
406
493
  const defs = customDefs ? [...BOT_DEFS.slice(0, -1), ...customDefs, BOT_DEFS[BOT_DEFS.length - 1]] : BOT_DEFS;
407
494
  for (const def of defs) {
408
495
  if (def.detect.test(ua)) {
409
- return { isBot: true, botName: def.name };
496
+ return { isBot: true, botName: def.name, botCategory: def.category };
410
497
  }
411
498
  }
412
- return { isBot: false, botName: "unknown" };
499
+ return { isBot: false, botName: "unknown", botCategory: "unknown" };
413
500
  }
414
501
 
415
502
  // src/constants/arch.ts
@@ -553,14 +640,22 @@ function platformFromUA(ua) {
553
640
  }
554
641
  return "unknown";
555
642
  }
643
+ function normalizeBCP47(raw) {
644
+ const parts = raw.replace(/_/g, "-").split("-");
645
+ return parts.map((p, i) => {
646
+ if (i === 0) return p.toLowerCase();
647
+ if (p.length === 4) return p[0].toUpperCase() + p.slice(1).toLowerCase();
648
+ return p.toUpperCase();
649
+ }).join("-");
650
+ }
556
651
  function languageFromUA(ua) {
652
+ const kwMatch = /\bLanguage\/([a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4}){1,2})\b/i.exec(ua);
653
+ if (kwMatch) return normalizeBCP47(kwMatch[1]);
557
654
  const re = /[;(]\s*([a-z]{2,3}(?:[-_][A-Za-z]{2,4})+)\s*[;)]/g;
558
655
  let m;
559
656
  while ((m = re.exec(ua)) !== null) {
560
657
  const parts = m[1].replace(/_/g, "-").split("-");
561
- if (parts.length >= 2) {
562
- return `${parts[0].toLowerCase()}-${parts.slice(1).map((p) => p.toUpperCase()).join("-")}`;
563
- }
658
+ if (parts.length >= 2) return normalizeBCP47(m[1]);
564
659
  }
565
660
  return "unknown";
566
661
  }
@@ -568,14 +663,15 @@ function parseUA(ua, options = {}) {
568
663
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
569
664
  const effectiveNav = (_a = options.ctx) != null ? _a : options.nav;
570
665
  const effectiveWindowsVersion = (_c = (_b = options.ctx) == null ? void 0 : _b.windowsVersion) != null ? _c : options.windowsVersion;
571
- const { browser: rawBrowser, version: rawVersion } = detectBrowser(ua);
572
- const { os: rawOs, osVersion: rawOsVersion } = detectOs(ua, effectiveWindowsVersion);
666
+ const { browser: rawBrowser, version: rawVersion, browserType } = detectBrowser(ua);
667
+ const { os: rawOs, osVersion: rawOsVersion, osVersionName } = detectOs(ua, effectiveWindowsVersion);
573
668
  let os = rawOs;
574
669
  let osVersion = rawOsVersion;
575
670
  const device = detectDevice(ua, effectiveNav);
671
+ const { vendor, model } = detectVendorModel(ua);
576
672
  const arch = detectArch(ua, (_d = options.ctx) != null ? _d : effectiveNav);
577
673
  const nav = effectiveNav;
578
- const { isBot, botName } = detectBot(ua, options.customBotDefs);
674
+ const { isBot, botName, botCategory } = detectBot(ua, options.customBotDefs);
579
675
  const isHeadless = detectHeadless(ua);
580
676
  const language = options.language || ((nav == null ? void 0 : nav.language) || (nav == null ? void 0 : nav.browserLanguage) ? getLanguage(nav) : "") || languageFromUA(ua);
581
677
  const platform = (nav == null ? void 0 : nav.platform) || platformFromUA(ua);
@@ -672,22 +768,39 @@ function parseUA(ua, options = {}) {
672
768
  }
673
769
  }
674
770
  }
675
- const engine = detectEngine(ua, browser, version);
771
+ const { engine, engineVersion } = detectEngine(ua, browser, version);
676
772
  const versionMajor = parseInt((_l = version.split(".")[0]) != null ? _l : "0", 10) || 0;
677
773
  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
+ const finalOsVersionName = os === "MacOS" || os === "Windows" ? (() => {
775
+ var _a2;
776
+ const map = (_a2 = OS_DEFS.find((d) => d.name === os)) == null ? void 0 : _a2.versionNames;
777
+ if (!map) return "unknown";
778
+ const parts = osVersion.split(".");
779
+ for (let len = parts.length; len >= 1; len--) {
780
+ const key = parts.slice(0, len).join(".");
781
+ if (Object.prototype.hasOwnProperty.call(map, key)) return map[key];
782
+ }
783
+ return "unknown";
784
+ })() : osVersionName;
678
785
  return {
679
786
  browser,
680
787
  version,
681
788
  versionMajor,
789
+ browserType: browser === "Brave" ? "browser" : browserType,
682
790
  engine,
791
+ engineVersion,
683
792
  os,
684
793
  osVersion,
794
+ osVersionName: finalOsVersionName,
685
795
  device,
796
+ vendor,
797
+ model,
686
798
  arch,
687
799
  isWebview: isWebview(ua),
688
800
  isHeadless,
689
801
  isBot,
690
802
  botName,
803
+ botCategory,
691
804
  language,
692
805
  platform,
693
806
  connectionType
@@ -953,8 +1066,11 @@ exports.default = src_default;
953
1066
  exports.detectArch = detectArch;
954
1067
  exports.detectBot = detectBot;
955
1068
  exports.detectBrowser = detectBrowser;
1069
+ exports.detectDevice = detectDevice;
1070
+ exports.detectEngine = detectEngine;
956
1071
  exports.detectHeadless = detectHeadless;
957
1072
  exports.detectOS = detectOs;
1073
+ exports.detectVendorModel = detectVendorModel;
958
1074
  exports.getEnvContext = getEnvContext;
959
1075
  exports.getLanguage = getLanguage;
960
1076
  exports.getNavContext = getNavContext;