ua-browser 1.0.2 → 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/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.2"};
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
@@ -209,13 +212,16 @@ var OS_DEFS = [
209
212
  { name: "MacOS", detect: /Macintosh/, versionPattern: /Mac OS X -?([\d_.]+)/ },
210
213
  { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
211
214
  // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
212
- // first, then HarmonyOS overrides it.
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.
213
217
  {
214
218
  name: "HarmonyOS",
215
219
  detect: /HarmonyOS/,
216
- versionPattern: /Android ([\d.]+)[;)]/,
217
- versionLookup: { "10": "2" }
220
+ versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
221
+ versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
218
222
  },
223
+ // OpenHarmony (open-source base) must come after HarmonyOS to override any earlier match.
224
+ { name: "OpenHarmony", detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
219
225
  { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
220
226
  // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
221
227
  // matches first, then Windows Phone overrides it.
@@ -249,7 +255,7 @@ function detectOs(ua, windowsVersion) {
249
255
  if (!matchedDef) return { os: "unknown", osVersion: "unknown" };
250
256
  let osVersion = "unknown";
251
257
  if (matchedDef.versionPattern) {
252
- const raw = extractVersion(ua, matchedDef.versionPattern);
258
+ const raw = Array.isArray(matchedDef.versionPattern) ? extractVersionFromPatterns(ua, matchedDef.versionPattern) : extractVersion(ua, matchedDef.versionPattern);
253
259
  if (raw !== null) {
254
260
  const normalised = raw.replace(/_/g, ".");
255
261
  if (matchedDef.versionLookup) {
@@ -259,6 +265,8 @@ function detectOs(ua, windowsVersion) {
259
265
  } else if (matchedDef.name === "Windows") {
260
266
  const n = parseInt(normalised, 10);
261
267
  osVersion = isNaN(n) ? "unknown" : n.toString();
268
+ } else {
269
+ osVersion = normalised;
262
270
  }
263
271
  } else {
264
272
  osVersion = normalised;
@@ -350,7 +358,33 @@ var ARCH_DEFS = [
350
358
  ];
351
359
 
352
360
  // src/detectors/arch.ts
353
- 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
+ }
354
388
  for (const def of ARCH_DEFS) {
355
389
  if (def.detect.test(ua)) {
356
390
  return def.name;
@@ -365,6 +399,13 @@ function detectHeadless(ua) {
365
399
  return HEADLESS_PATTERN.test(ua);
366
400
  }
367
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
+
368
409
  // src/utils/navigator.ts
369
410
  function getNavContext() {
370
411
  if (typeof navigator === "undefined") {
@@ -389,17 +430,19 @@ function getLanguage(nav) {
389
430
 
390
431
  // src/parse.ts
391
432
  function parseUA(ua, options = {}) {
392
- var _a, _b, _c, _d, _e;
393
- 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;
394
436
  const { browser: rawBrowser, version: rawVersion } = detectBrowser(ua);
395
- const { os, osVersion: rawOsVersion } = detectOs(ua, windowsVersion);
437
+ const { os, osVersion: rawOsVersion } = detectOs(ua, effectiveWindowsVersion);
396
438
  let osVersion = rawOsVersion;
397
- const device = detectDevice(ua, nav);
398
- const arch = detectArch(ua);
439
+ const device = detectDevice(ua, effectiveNav);
440
+ const arch = detectArch(ua, options.ctx);
441
+ const nav = effectiveNav;
399
442
  const { isBot, botName } = detectBot(ua);
400
443
  const isHeadless = detectHeadless(ua);
401
444
  const language = nav ? getLanguage(nav) : "unknown";
402
- 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";
403
446
  let browser = rawBrowser;
404
447
  let version = rawVersion;
405
448
  if (nav) {
@@ -421,7 +464,7 @@ function parseUA(ua, options = {}) {
421
464
  }
422
465
  }
423
466
  if (is360) {
424
- const saveDataEnabled = ((_b = nav.connection) == null ? void 0 : _b.saveData) === true;
467
+ const saveDataEnabled = ((_e = nav.connection) == null ? void 0 : _e.saveData) === true;
425
468
  if (getMimeType(nav, "application/gameplugin") || !saveDataEnabled) {
426
469
  browser = "360SE";
427
470
  } else {
@@ -434,8 +477,8 @@ function parseUA(ua, options = {}) {
434
477
  }
435
478
  if (browser === "Baidu" && /(Opera|OPR|OPT)/.test(ua)) {
436
479
  browser = "Opera";
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";
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";
439
482
  }
440
483
  if (browser === "Chrome" && /\S+Browser\//.test(ua)) {
441
484
  const m = /(\S+Browser)\/([\d.]+)/.exec(ua);
@@ -475,7 +518,7 @@ function parseUA(ua, options = {}) {
475
518
  osVersion,
476
519
  device,
477
520
  arch,
478
- isWebview: /; wv/.test(ua),
521
+ isWebview: isWebview(ua),
479
522
  isHeadless,
480
523
  isBot,
481
524
  botName,
@@ -499,9 +542,165 @@ async function getWindowsVersion(nav) {
499
542
  }
500
543
  }
501
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
+
502
702
  // src/index.ts
503
703
  var { version: VERSION } = package_default;
504
- var isWebview = (ua) => /; wv/.test(ua);
505
704
  var isWechatMiniapp = () => {
506
705
  try {
507
706
  return typeof __wxjs_environment !== "undefined" && __wxjs_environment === "miniprogram";
@@ -519,16 +718,19 @@ uaBrowser.getLanguage = () => getLanguage(getNavContext());
519
718
  uaBrowser.VERSION = VERSION;
520
719
  var src_default = uaBrowser;
521
720
 
721
+ exports.ACCEPT_CH = ACCEPT_CH;
522
722
  exports.VERSION = VERSION;
523
723
  exports.default = src_default;
524
724
  exports.detectArch = detectArch;
525
725
  exports.detectBot = detectBot;
526
726
  exports.detectHeadless = detectHeadless;
727
+ exports.getEnvContext = getEnvContext;
527
728
  exports.getLanguage = getLanguage;
528
729
  exports.getNavContext = getNavContext;
529
730
  exports.getWindowsVersion = getWindowsVersion;
530
731
  exports.isWebview = isWebview;
531
732
  exports.isWechatMiniapp = isWechatMiniapp;
733
+ exports.parseHeaders = parseHeaders;
532
734
  exports.parseUA = parseUA;
533
735
  //# sourceMappingURL=index.cjs.map
534
736
  //# sourceMappingURL=index.cjs.map