ua-browser 1.0.1 → 1.1.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.md CHANGED
@@ -16,7 +16,7 @@ Detect browser, OS, device type, rendering engine, CPU architecture, bots, and h
16
16
  - **Zero dependencies** — no runtime dependencies, tiny bundle size after gzip
17
17
  - **Pure function** — `parseUA()` has no global state, works seamlessly with SSR / Node.js
18
18
  - **TypeScript** — full type definitions with precise literal union types (`BrowserName`, `OsName`, etc.)
19
- - **Tree-shakeable** — import only what you need
19
+ - **Tree-shakeable** — named exports + `sideEffects: false`, unused code eliminated by Vite / Rollup / webpack 5+
20
20
 
21
21
  ## Installation
22
22
 
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.1"};
7
+ version: "1.1.0"};
8
8
 
9
9
  // src/constants/browsers.ts
10
10
  var BROWSER_DEFS = [
@@ -166,7 +166,10 @@ var ENGINE_DEFS = [
166
166
  { name: "Trident", detect: /(Trident|NET CLR)/ },
167
167
  { name: "Presto", detect: /Presto/ },
168
168
  { name: "Gecko", detect: /Gecko\// },
169
- { name: "WebKit", detect: /AppleWebKit/ }
169
+ { name: "WebKit", detect: /AppleWebKit/ },
170
+ // ArkWeb is HarmonyOS Next's rendering engine; only present in UAs that explicitly contain the
171
+ // token. It must come last so it overrides the WebKit entry that also matches those UAs.
172
+ { name: "ArkWeb", detect: /ArkWeb/ }
170
173
  ];
171
174
 
172
175
  // src/detectors/engine.ts
@@ -197,23 +200,31 @@ var OS_DEFS = [
197
200
  { name: "Symbian", detect: /Symbian/, versionPattern: null },
198
201
  { name: "MeeGo", detect: /MeeGo/, versionPattern: null },
199
202
  { name: "BlackBerry", detect: /(BlackBerry|RIM)/, versionPattern: null },
200
- { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ },
201
203
  { name: "FreeBSD", detect: /FreeBSD/, versionPattern: null },
202
204
  { name: "Debian", detect: /Debian/, versionPattern: /Debian\/([\d.]+)/ },
203
205
  { name: "Ubuntu", detect: /Ubuntu/, versionPattern: null },
204
- { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
206
+ // Linux must come before Chrome OS: Chrome OS UAs contain "X11", so Linux matches first,
207
+ // then Chrome OS overrides it.
205
208
  { name: "Linux", detect: /(Linux|X11)/, versionPattern: null },
209
+ { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
206
210
  { name: "Tizen", detect: /Tizen/, versionPattern: /Tizen ([\d.]+)/ },
207
211
  { name: "iOS", detect: /like Mac OS X/, versionPattern: /OS ([\d_]+) like/ },
208
212
  { name: "MacOS", detect: /Macintosh/, versionPattern: /Mac OS X -?([\d_.]+)/ },
213
+ { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
214
+ // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
215
+ // first, then HarmonyOS overrides it. versionPattern tries direct extraction first (5.0+
216
+ // pure HarmonyOS UAs don't have Android token), then falls back to Android version + lookup.
209
217
  {
210
218
  name: "HarmonyOS",
211
219
  detect: /HarmonyOS/,
212
- versionPattern: /Android ([\d.]+)[;)]/,
213
- versionLookup: { "10": "2" }
220
+ versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
221
+ versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
214
222
  },
215
- { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
223
+ // OpenHarmony (open-source base) must come after HarmonyOS to override any earlier match.
224
+ { name: "OpenHarmony", detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
216
225
  { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
226
+ // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
227
+ // matches first, then Windows Phone overrides it.
217
228
  {
218
229
  name: "Windows",
219
230
  detect: /Windows/,
@@ -229,7 +240,8 @@ var OS_DEFS = [
229
240
  "5.1": "XP",
230
241
  "5.0": "2000"
231
242
  }
232
- }
243
+ },
244
+ { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
233
245
  ];
234
246
 
235
247
  // src/detectors/os.ts
@@ -243,7 +255,7 @@ function detectOs(ua, windowsVersion) {
243
255
  if (!matchedDef) return { os: "unknown", osVersion: "unknown" };
244
256
  let osVersion = "unknown";
245
257
  if (matchedDef.versionPattern) {
246
- const raw = extractVersion(ua, matchedDef.versionPattern);
258
+ const raw = Array.isArray(matchedDef.versionPattern) ? extractVersionFromPatterns(ua, matchedDef.versionPattern) : extractVersion(ua, matchedDef.versionPattern);
247
259
  if (raw !== null) {
248
260
  const normalised = raw.replace(/_/g, ".");
249
261
  if (matchedDef.versionLookup) {
@@ -253,6 +265,8 @@ function detectOs(ua, windowsVersion) {
253
265
  } else if (matchedDef.name === "Windows") {
254
266
  const n = parseInt(normalised, 10);
255
267
  osVersion = isNaN(n) ? "unknown" : n.toString();
268
+ } else {
269
+ osVersion = normalised;
256
270
  }
257
271
  } else {
258
272
  osVersion = normalised;
@@ -344,7 +358,33 @@ var ARCH_DEFS = [
344
358
  ];
345
359
 
346
360
  // src/detectors/arch.ts
347
- function detectArch(ua) {
361
+ function detectArch(ua, ctx) {
362
+ if (ctx == null ? void 0 : ctx.highEntropyData) {
363
+ const { architecture, bitness } = ctx.highEntropyData;
364
+ if (architecture === "arm") {
365
+ return bitness === "64" ? "arm64" : "arm";
366
+ }
367
+ if (architecture === "x86") {
368
+ return bitness === "64" ? "x86_64" : "x86";
369
+ }
370
+ }
371
+ const renderer = ctx == null ? void 0 : ctx.webglRenderer;
372
+ if (renderer) {
373
+ if (/Apple\s+(M\d|A\d{1,2}[A-Z]?)\b/i.test(renderer) || /APPLE M\d/i.test(renderer)) {
374
+ return "arm64";
375
+ }
376
+ if (/\b(Intel|AMD|NVIDIA|Radeon)\b/i.test(renderer)) {
377
+ return "x86_64";
378
+ }
379
+ }
380
+ const platform = ctx == null ? void 0 : ctx.platform;
381
+ if (platform) {
382
+ if (/iPhone|iPad|iPod/.test(platform)) return "arm64";
383
+ if (/arm64|aarch64/i.test(platform)) return "arm64";
384
+ if (/arm/i.test(platform)) return "arm";
385
+ if (/Win64|WOW64|x86_64/i.test(platform)) return "x86_64";
386
+ if (/Win32|i686/i.test(platform)) return "x86";
387
+ }
348
388
  for (const def of ARCH_DEFS) {
349
389
  if (def.detect.test(ua)) {
350
390
  return def.name;
@@ -359,6 +399,13 @@ function detectHeadless(ua) {
359
399
  return HEADLESS_PATTERN.test(ua);
360
400
  }
361
401
 
402
+ // src/detectors/webview.ts
403
+ function isWebview(ua) {
404
+ if (/; wv/.test(ua)) return true;
405
+ if (/like Mac OS X/.test(ua) && !/Version\//.test(ua) && !/Safari\//.test(ua)) return true;
406
+ return false;
407
+ }
408
+
362
409
  // src/utils/navigator.ts
363
410
  function getNavContext() {
364
411
  if (typeof navigator === "undefined") {
@@ -383,16 +430,19 @@ function getLanguage(nav) {
383
430
 
384
431
  // src/parse.ts
385
432
  function parseUA(ua, options = {}) {
386
- var _a, _b, _c, _d, _e;
387
- const { nav, windowsVersion } = options;
433
+ var _a, _b, _c, _d, _e, _f, _g, _h;
434
+ const effectiveNav = (_a = options.ctx) != null ? _a : options.nav;
435
+ const effectiveWindowsVersion = (_c = (_b = options.ctx) == null ? void 0 : _b.windowsVersion) != null ? _c : options.windowsVersion;
388
436
  const { browser: rawBrowser, version: rawVersion } = detectBrowser(ua);
389
- const { os, osVersion } = detectOs(ua, windowsVersion);
390
- const device = detectDevice(ua, nav);
391
- const arch = detectArch(ua);
437
+ const { os, osVersion: rawOsVersion } = detectOs(ua, effectiveWindowsVersion);
438
+ let osVersion = rawOsVersion;
439
+ const device = detectDevice(ua, effectiveNav);
440
+ const arch = detectArch(ua, options.ctx);
441
+ const nav = effectiveNav;
392
442
  const { isBot, botName } = detectBot(ua);
393
443
  const isHeadless = detectHeadless(ua);
394
444
  const language = nav ? getLanguage(nav) : "unknown";
395
- const platform = (_a = nav == null ? void 0 : nav.platform) != null ? _a : "unknown";
445
+ const platform = (_d = nav == null ? void 0 : nav.platform) != null ? _d : "unknown";
396
446
  let browser = rawBrowser;
397
447
  let version = rawVersion;
398
448
  if (nav) {
@@ -414,7 +464,7 @@ function parseUA(ua, options = {}) {
414
464
  }
415
465
  }
416
466
  if (is360) {
417
- const saveDataEnabled = ((_b = nav.connection) == null ? void 0 : _b.saveData) === true;
467
+ const saveDataEnabled = ((_e = nav.connection) == null ? void 0 : _e.saveData) === true;
418
468
  if (getMimeType(nav, "application/gameplugin") || !saveDataEnabled) {
419
469
  browser = "360SE";
420
470
  } else {
@@ -427,8 +477,8 @@ function parseUA(ua, options = {}) {
427
477
  }
428
478
  if (browser === "Baidu" && /(Opera|OPR|OPT)/.test(ua)) {
429
479
  browser = "Opera";
430
- const opVer = (_d = (_c = /OPR\/([\d.]+)/.exec(ua)) != null ? _c : /OPT\/([\d.]+)/.exec(ua)) != null ? _d : /Opera\/([\d.]+)/.exec(ua);
431
- version = (_e = opVer == null ? void 0 : opVer[1]) != null ? _e : "unknown";
480
+ const opVer = (_g = (_f = /OPR\/([\d.]+)/.exec(ua)) != null ? _f : /OPT\/([\d.]+)/.exec(ua)) != null ? _g : /Opera\/([\d.]+)/.exec(ua);
481
+ version = (_h = opVer == null ? void 0 : opVer[1]) != null ? _h : "unknown";
432
482
  }
433
483
  if (browser === "Chrome" && /\S+Browser\//.test(ua)) {
434
484
  const m = /(\S+Browser)\/([\d.]+)/.exec(ua);
@@ -453,6 +503,12 @@ function parseUA(ua, options = {}) {
453
503
  } catch (e) {
454
504
  }
455
505
  }
506
+ if (os === "iOS" && browser === "Safari") {
507
+ const m = /Version\/([\d.]+)/.exec(ua);
508
+ if (m && parseInt(m[1], 10) > parseInt(osVersion, 10)) {
509
+ osVersion = m[1];
510
+ }
511
+ }
456
512
  const engine = detectEngine(ua, browser, version);
457
513
  return {
458
514
  browser,
@@ -462,7 +518,7 @@ function parseUA(ua, options = {}) {
462
518
  osVersion,
463
519
  device,
464
520
  arch,
465
- isWebview: /; wv/.test(ua),
521
+ isWebview: isWebview(ua),
466
522
  isHeadless,
467
523
  isBot,
468
524
  botName,
@@ -486,9 +542,165 @@ async function getWindowsVersion(nav) {
486
542
  }
487
543
  }
488
544
 
545
+ // src/utils/env-context.ts
546
+ var OS_FONTS = [
547
+ ".AppleSystemUIFont",
548
+ "Segoe UI",
549
+ "Noto Color Emoji",
550
+ "Ubuntu",
551
+ "HarmonyOS Sans"
552
+ ];
553
+ function probeFonts() {
554
+ try {
555
+ const canvas = document.createElement("canvas");
556
+ const ctx = canvas.getContext("2d");
557
+ if (!ctx) return {};
558
+ const TEST = "mmmmmmmmmmlli";
559
+ ctx.font = "72px monospace";
560
+ const baseline = ctx.measureText(TEST).width;
561
+ const result = {};
562
+ for (const font of OS_FONTS) {
563
+ ctx.font = `72px '${font}', monospace`;
564
+ result[font] = ctx.measureText(TEST).width !== baseline;
565
+ }
566
+ return result;
567
+ } catch (e) {
568
+ return {};
569
+ }
570
+ }
571
+ function getWebGLInfo() {
572
+ var _a;
573
+ try {
574
+ const canvas = document.createElement("canvas");
575
+ const gl = (_a = canvas.getContext("webgl")) != null ? _a : canvas.getContext("experimental-webgl");
576
+ if (!gl) return {};
577
+ const ext = gl.getExtension("WEBGL_debug_renderer_info");
578
+ if (!ext) return {};
579
+ return {
580
+ renderer: gl.getParameter(ext.UNMASKED_RENDERER_WEBGL),
581
+ vendor: gl.getParameter(ext.UNMASKED_VENDOR_WEBGL)
582
+ };
583
+ } catch (e) {
584
+ return {};
585
+ }
586
+ }
587
+ function getPointerType() {
588
+ try {
589
+ if (window.matchMedia("(pointer: coarse)").matches) return "coarse";
590
+ if (window.matchMedia("(pointer: fine)").matches) return "fine";
591
+ if (window.matchMedia("(pointer: none)").matches) return "none";
592
+ } catch (e) {
593
+ }
594
+ return void 0;
595
+ }
596
+ function getHoverCapability() {
597
+ try {
598
+ return window.matchMedia("(hover: hover)").matches;
599
+ } catch (e) {
600
+ return void 0;
601
+ }
602
+ }
603
+ function deriveWindowsVersion(platformVersion) {
604
+ var _a;
605
+ if (!platformVersion) return null;
606
+ const major = parseInt((_a = platformVersion.split(".")[0]) != null ? _a : "0", 10);
607
+ return isNaN(major) ? null : major >= 13 ? "11" : "10";
608
+ }
609
+ async function getEnvContext() {
610
+ const base = getNavContext();
611
+ const ctx = { ...base };
612
+ if (typeof navigator !== "undefined") {
613
+ ctx.hardwareConcurrency = navigator.hardwareConcurrency;
614
+ ctx.deviceMemory = navigator.deviceMemory;
615
+ }
616
+ if (typeof window !== "undefined") {
617
+ ctx.devicePixelRatio = window.devicePixelRatio;
618
+ ctx.pointerType = getPointerType();
619
+ ctx.hoverCapability = getHoverCapability();
620
+ ctx.fontProbes = probeFonts();
621
+ const { renderer, vendor } = getWebGLInfo();
622
+ if (renderer !== void 0) ctx.webglRenderer = renderer;
623
+ if (vendor !== void 0) ctx.webglVendor = vendor;
624
+ }
625
+ if (base.userAgentData) {
626
+ try {
627
+ const data = await base.userAgentData.getHighEntropyValues([
628
+ "architecture",
629
+ "bitness",
630
+ "model",
631
+ "platformVersion",
632
+ "fullVersionList"
633
+ ]);
634
+ ctx.highEntropyData = {
635
+ architecture: data["architecture"],
636
+ bitness: data["bitness"],
637
+ model: data["model"],
638
+ platformVersion: data["platformVersion"],
639
+ fullVersionList: data["fullVersionList"]
640
+ };
641
+ if (base.userAgentData.platform === "Windows") {
642
+ ctx.windowsVersion = deriveWindowsVersion(data["platformVersion"]);
643
+ }
644
+ } catch (e) {
645
+ }
646
+ }
647
+ return ctx;
648
+ }
649
+
650
+ // src/parse-headers.ts
651
+ var ACCEPT_CH = [
652
+ "Sec-CH-UA-Arch",
653
+ "Sec-CH-UA-Bitness",
654
+ "Sec-CH-UA-Platform-Version",
655
+ "Sec-CH-UA-Model",
656
+ "Sec-CH-UA-Full-Version-List"
657
+ ].join(", ");
658
+ function unquote(value) {
659
+ if (value == null) return void 0;
660
+ return value.replace(/^"|"$/g, "").trim() || void 0;
661
+ }
662
+ function deriveWindowsVersion2(platformVersion) {
663
+ var _a;
664
+ if (!platformVersion) return null;
665
+ const major = parseInt((_a = platformVersion.split(".")[0]) != null ? _a : "0", 10);
666
+ return isNaN(major) ? null : major >= 13 ? "11" : "10";
667
+ }
668
+ function parseHeaders(headers) {
669
+ var _a, _b;
670
+ const normalised = {};
671
+ for (const key of Object.keys(headers)) {
672
+ normalised[key.toLowerCase()] = headers[key];
673
+ }
674
+ const get = (name) => {
675
+ const v = normalised[name.toLowerCase()];
676
+ return Array.isArray(v) ? v[0] : v;
677
+ };
678
+ const ua = (_a = get("user-agent")) != null ? _a : "";
679
+ const architecture = unquote(get("sec-ch-ua-arch"));
680
+ const bitness = unquote(get("sec-ch-ua-bitness"));
681
+ const model = unquote(get("sec-ch-ua-model"));
682
+ const platformVersion = unquote(get("sec-ch-ua-platform-version"));
683
+ const platform = (_b = unquote(get("sec-ch-ua-platform"))) != null ? _b : "";
684
+ const highEntropyData = {};
685
+ if (architecture !== void 0) highEntropyData.architecture = architecture;
686
+ if (bitness !== void 0) highEntropyData.bitness = bitness;
687
+ if (model !== void 0) highEntropyData.model = model;
688
+ if (platformVersion !== void 0) highEntropyData.platformVersion = platformVersion;
689
+ const isMobile = get("sec-ch-ua-mobile") === "?1";
690
+ const windowsVersion = platform === "Windows" ? deriveWindowsVersion2(platformVersion) : null;
691
+ const ctx = {
692
+ userAgent: ua,
693
+ platform,
694
+ language: "",
695
+ maxTouchPoints: isMobile ? 1 : 0,
696
+ highEntropyData: Object.keys(highEntropyData).length > 0 ? highEntropyData : void 0,
697
+ windowsVersion
698
+ };
699
+ return parseUA(ua, { ctx });
700
+ }
701
+
489
702
  // src/index.ts
490
703
  var { version: VERSION } = package_default;
491
- var isWebview = (ua) => /; wv/.test(ua);
492
704
  var isWechatMiniapp = () => {
493
705
  try {
494
706
  return typeof __wxjs_environment !== "undefined" && __wxjs_environment === "miniprogram";
@@ -506,16 +718,19 @@ uaBrowser.getLanguage = () => getLanguage(getNavContext());
506
718
  uaBrowser.VERSION = VERSION;
507
719
  var src_default = uaBrowser;
508
720
 
721
+ exports.ACCEPT_CH = ACCEPT_CH;
509
722
  exports.VERSION = VERSION;
510
723
  exports.default = src_default;
511
724
  exports.detectArch = detectArch;
512
725
  exports.detectBot = detectBot;
513
726
  exports.detectHeadless = detectHeadless;
727
+ exports.getEnvContext = getEnvContext;
514
728
  exports.getLanguage = getLanguage;
515
729
  exports.getNavContext = getNavContext;
516
730
  exports.getWindowsVersion = getWindowsVersion;
517
731
  exports.isWebview = isWebview;
518
732
  exports.isWechatMiniapp = isWechatMiniapp;
733
+ exports.parseHeaders = parseHeaders;
519
734
  exports.parseUA = parseUA;
520
735
  //# sourceMappingURL=index.cjs.map
521
736
  //# sourceMappingURL=index.cjs.map