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.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var package_default = {
3
- version: "1.0.2"};
3
+ version: "1.1.0"};
4
4
 
5
5
  // src/constants/browsers.ts
6
6
  var BROWSER_DEFS = [
@@ -162,7 +162,10 @@ var ENGINE_DEFS = [
162
162
  { name: "Trident", detect: /(Trident|NET CLR)/ },
163
163
  { name: "Presto", detect: /Presto/ },
164
164
  { name: "Gecko", detect: /Gecko\// },
165
- { name: "WebKit", detect: /AppleWebKit/ }
165
+ { name: "WebKit", detect: /AppleWebKit/ },
166
+ // ArkWeb is HarmonyOS Next's rendering engine; only present in UAs that explicitly contain the
167
+ // token. It must come last so it overrides the WebKit entry that also matches those UAs.
168
+ { name: "ArkWeb", detect: /ArkWeb/ }
166
169
  ];
167
170
 
168
171
  // src/detectors/engine.ts
@@ -205,13 +208,16 @@ var OS_DEFS = [
205
208
  { name: "MacOS", detect: /Macintosh/, versionPattern: /Mac OS X -?([\d_.]+)/ },
206
209
  { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
207
210
  // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
208
- // first, then HarmonyOS overrides it.
211
+ // first, then HarmonyOS overrides it. versionPattern tries direct extraction first (5.0+
212
+ // pure HarmonyOS UAs don't have Android token), then falls back to Android version + lookup.
209
213
  {
210
214
  name: "HarmonyOS",
211
215
  detect: /HarmonyOS/,
212
- versionPattern: /Android ([\d.]+)[;)]/,
213
- versionLookup: { "10": "2" }
216
+ versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
217
+ versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
214
218
  },
219
+ // OpenHarmony (open-source base) must come after HarmonyOS to override any earlier match.
220
+ { name: "OpenHarmony", detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
215
221
  { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
216
222
  // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
217
223
  // matches first, then Windows Phone overrides it.
@@ -245,7 +251,7 @@ function detectOs(ua, windowsVersion) {
245
251
  if (!matchedDef) return { os: "unknown", osVersion: "unknown" };
246
252
  let osVersion = "unknown";
247
253
  if (matchedDef.versionPattern) {
248
- const raw = extractVersion(ua, matchedDef.versionPattern);
254
+ const raw = Array.isArray(matchedDef.versionPattern) ? extractVersionFromPatterns(ua, matchedDef.versionPattern) : extractVersion(ua, matchedDef.versionPattern);
249
255
  if (raw !== null) {
250
256
  const normalised = raw.replace(/_/g, ".");
251
257
  if (matchedDef.versionLookup) {
@@ -255,6 +261,8 @@ function detectOs(ua, windowsVersion) {
255
261
  } else if (matchedDef.name === "Windows") {
256
262
  const n = parseInt(normalised, 10);
257
263
  osVersion = isNaN(n) ? "unknown" : n.toString();
264
+ } else {
265
+ osVersion = normalised;
258
266
  }
259
267
  } else {
260
268
  osVersion = normalised;
@@ -346,7 +354,33 @@ var ARCH_DEFS = [
346
354
  ];
347
355
 
348
356
  // src/detectors/arch.ts
349
- function detectArch(ua) {
357
+ function detectArch(ua, ctx) {
358
+ if (ctx == null ? void 0 : ctx.highEntropyData) {
359
+ const { architecture, bitness } = ctx.highEntropyData;
360
+ if (architecture === "arm") {
361
+ return bitness === "64" ? "arm64" : "arm";
362
+ }
363
+ if (architecture === "x86") {
364
+ return bitness === "64" ? "x86_64" : "x86";
365
+ }
366
+ }
367
+ const renderer = ctx == null ? void 0 : ctx.webglRenderer;
368
+ if (renderer) {
369
+ if (/Apple\s+(M\d|A\d{1,2}[A-Z]?)\b/i.test(renderer) || /APPLE M\d/i.test(renderer)) {
370
+ return "arm64";
371
+ }
372
+ if (/\b(Intel|AMD|NVIDIA|Radeon)\b/i.test(renderer)) {
373
+ return "x86_64";
374
+ }
375
+ }
376
+ const platform = ctx == null ? void 0 : ctx.platform;
377
+ if (platform) {
378
+ if (/iPhone|iPad|iPod/.test(platform)) return "arm64";
379
+ if (/arm64|aarch64/i.test(platform)) return "arm64";
380
+ if (/arm/i.test(platform)) return "arm";
381
+ if (/Win64|WOW64|x86_64/i.test(platform)) return "x86_64";
382
+ if (/Win32|i686/i.test(platform)) return "x86";
383
+ }
350
384
  for (const def of ARCH_DEFS) {
351
385
  if (def.detect.test(ua)) {
352
386
  return def.name;
@@ -361,6 +395,13 @@ function detectHeadless(ua) {
361
395
  return HEADLESS_PATTERN.test(ua);
362
396
  }
363
397
 
398
+ // src/detectors/webview.ts
399
+ function isWebview(ua) {
400
+ if (/; wv/.test(ua)) return true;
401
+ if (/like Mac OS X/.test(ua) && !/Version\//.test(ua) && !/Safari\//.test(ua)) return true;
402
+ return false;
403
+ }
404
+
364
405
  // src/utils/navigator.ts
365
406
  function getNavContext() {
366
407
  if (typeof navigator === "undefined") {
@@ -385,17 +426,19 @@ function getLanguage(nav) {
385
426
 
386
427
  // src/parse.ts
387
428
  function parseUA(ua, options = {}) {
388
- var _a, _b, _c, _d, _e;
389
- const { nav, windowsVersion } = options;
429
+ var _a, _b, _c, _d, _e, _f, _g, _h;
430
+ const effectiveNav = (_a = options.ctx) != null ? _a : options.nav;
431
+ const effectiveWindowsVersion = (_c = (_b = options.ctx) == null ? void 0 : _b.windowsVersion) != null ? _c : options.windowsVersion;
390
432
  const { browser: rawBrowser, version: rawVersion } = detectBrowser(ua);
391
- const { os, osVersion: rawOsVersion } = detectOs(ua, windowsVersion);
433
+ const { os, osVersion: rawOsVersion } = detectOs(ua, effectiveWindowsVersion);
392
434
  let osVersion = rawOsVersion;
393
- const device = detectDevice(ua, nav);
394
- const arch = detectArch(ua);
435
+ const device = detectDevice(ua, effectiveNav);
436
+ const arch = detectArch(ua, options.ctx);
437
+ const nav = effectiveNav;
395
438
  const { isBot, botName } = detectBot(ua);
396
439
  const isHeadless = detectHeadless(ua);
397
440
  const language = nav ? getLanguage(nav) : "unknown";
398
- const platform = (_a = nav == null ? void 0 : nav.platform) != null ? _a : "unknown";
441
+ const platform = (_d = nav == null ? void 0 : nav.platform) != null ? _d : "unknown";
399
442
  let browser = rawBrowser;
400
443
  let version = rawVersion;
401
444
  if (nav) {
@@ -417,7 +460,7 @@ function parseUA(ua, options = {}) {
417
460
  }
418
461
  }
419
462
  if (is360) {
420
- const saveDataEnabled = ((_b = nav.connection) == null ? void 0 : _b.saveData) === true;
463
+ const saveDataEnabled = ((_e = nav.connection) == null ? void 0 : _e.saveData) === true;
421
464
  if (getMimeType(nav, "application/gameplugin") || !saveDataEnabled) {
422
465
  browser = "360SE";
423
466
  } else {
@@ -430,8 +473,8 @@ function parseUA(ua, options = {}) {
430
473
  }
431
474
  if (browser === "Baidu" && /(Opera|OPR|OPT)/.test(ua)) {
432
475
  browser = "Opera";
433
- const opVer = (_d = (_c = /OPR\/([\d.]+)/.exec(ua)) != null ? _c : /OPT\/([\d.]+)/.exec(ua)) != null ? _d : /Opera\/([\d.]+)/.exec(ua);
434
- version = (_e = opVer == null ? void 0 : opVer[1]) != null ? _e : "unknown";
476
+ const opVer = (_g = (_f = /OPR\/([\d.]+)/.exec(ua)) != null ? _f : /OPT\/([\d.]+)/.exec(ua)) != null ? _g : /Opera\/([\d.]+)/.exec(ua);
477
+ version = (_h = opVer == null ? void 0 : opVer[1]) != null ? _h : "unknown";
435
478
  }
436
479
  if (browser === "Chrome" && /\S+Browser\//.test(ua)) {
437
480
  const m = /(\S+Browser)\/([\d.]+)/.exec(ua);
@@ -471,7 +514,7 @@ function parseUA(ua, options = {}) {
471
514
  osVersion,
472
515
  device,
473
516
  arch,
474
- isWebview: /; wv/.test(ua),
517
+ isWebview: isWebview(ua),
475
518
  isHeadless,
476
519
  isBot,
477
520
  botName,
@@ -495,9 +538,165 @@ async function getWindowsVersion(nav) {
495
538
  }
496
539
  }
497
540
 
541
+ // src/utils/env-context.ts
542
+ var OS_FONTS = [
543
+ ".AppleSystemUIFont",
544
+ "Segoe UI",
545
+ "Noto Color Emoji",
546
+ "Ubuntu",
547
+ "HarmonyOS Sans"
548
+ ];
549
+ function probeFonts() {
550
+ try {
551
+ const canvas = document.createElement("canvas");
552
+ const ctx = canvas.getContext("2d");
553
+ if (!ctx) return {};
554
+ const TEST = "mmmmmmmmmmlli";
555
+ ctx.font = "72px monospace";
556
+ const baseline = ctx.measureText(TEST).width;
557
+ const result = {};
558
+ for (const font of OS_FONTS) {
559
+ ctx.font = `72px '${font}', monospace`;
560
+ result[font] = ctx.measureText(TEST).width !== baseline;
561
+ }
562
+ return result;
563
+ } catch (e) {
564
+ return {};
565
+ }
566
+ }
567
+ function getWebGLInfo() {
568
+ var _a;
569
+ try {
570
+ const canvas = document.createElement("canvas");
571
+ const gl = (_a = canvas.getContext("webgl")) != null ? _a : canvas.getContext("experimental-webgl");
572
+ if (!gl) return {};
573
+ const ext = gl.getExtension("WEBGL_debug_renderer_info");
574
+ if (!ext) return {};
575
+ return {
576
+ renderer: gl.getParameter(ext.UNMASKED_RENDERER_WEBGL),
577
+ vendor: gl.getParameter(ext.UNMASKED_VENDOR_WEBGL)
578
+ };
579
+ } catch (e) {
580
+ return {};
581
+ }
582
+ }
583
+ function getPointerType() {
584
+ try {
585
+ if (window.matchMedia("(pointer: coarse)").matches) return "coarse";
586
+ if (window.matchMedia("(pointer: fine)").matches) return "fine";
587
+ if (window.matchMedia("(pointer: none)").matches) return "none";
588
+ } catch (e) {
589
+ }
590
+ return void 0;
591
+ }
592
+ function getHoverCapability() {
593
+ try {
594
+ return window.matchMedia("(hover: hover)").matches;
595
+ } catch (e) {
596
+ return void 0;
597
+ }
598
+ }
599
+ function deriveWindowsVersion(platformVersion) {
600
+ var _a;
601
+ if (!platformVersion) return null;
602
+ const major = parseInt((_a = platformVersion.split(".")[0]) != null ? _a : "0", 10);
603
+ return isNaN(major) ? null : major >= 13 ? "11" : "10";
604
+ }
605
+ async function getEnvContext() {
606
+ const base = getNavContext();
607
+ const ctx = { ...base };
608
+ if (typeof navigator !== "undefined") {
609
+ ctx.hardwareConcurrency = navigator.hardwareConcurrency;
610
+ ctx.deviceMemory = navigator.deviceMemory;
611
+ }
612
+ if (typeof window !== "undefined") {
613
+ ctx.devicePixelRatio = window.devicePixelRatio;
614
+ ctx.pointerType = getPointerType();
615
+ ctx.hoverCapability = getHoverCapability();
616
+ ctx.fontProbes = probeFonts();
617
+ const { renderer, vendor } = getWebGLInfo();
618
+ if (renderer !== void 0) ctx.webglRenderer = renderer;
619
+ if (vendor !== void 0) ctx.webglVendor = vendor;
620
+ }
621
+ if (base.userAgentData) {
622
+ try {
623
+ const data = await base.userAgentData.getHighEntropyValues([
624
+ "architecture",
625
+ "bitness",
626
+ "model",
627
+ "platformVersion",
628
+ "fullVersionList"
629
+ ]);
630
+ ctx.highEntropyData = {
631
+ architecture: data["architecture"],
632
+ bitness: data["bitness"],
633
+ model: data["model"],
634
+ platformVersion: data["platformVersion"],
635
+ fullVersionList: data["fullVersionList"]
636
+ };
637
+ if (base.userAgentData.platform === "Windows") {
638
+ ctx.windowsVersion = deriveWindowsVersion(data["platformVersion"]);
639
+ }
640
+ } catch (e) {
641
+ }
642
+ }
643
+ return ctx;
644
+ }
645
+
646
+ // src/parse-headers.ts
647
+ var ACCEPT_CH = [
648
+ "Sec-CH-UA-Arch",
649
+ "Sec-CH-UA-Bitness",
650
+ "Sec-CH-UA-Platform-Version",
651
+ "Sec-CH-UA-Model",
652
+ "Sec-CH-UA-Full-Version-List"
653
+ ].join(", ");
654
+ function unquote(value) {
655
+ if (value == null) return void 0;
656
+ return value.replace(/^"|"$/g, "").trim() || void 0;
657
+ }
658
+ function deriveWindowsVersion2(platformVersion) {
659
+ var _a;
660
+ if (!platformVersion) return null;
661
+ const major = parseInt((_a = platformVersion.split(".")[0]) != null ? _a : "0", 10);
662
+ return isNaN(major) ? null : major >= 13 ? "11" : "10";
663
+ }
664
+ function parseHeaders(headers) {
665
+ var _a, _b;
666
+ const normalised = {};
667
+ for (const key of Object.keys(headers)) {
668
+ normalised[key.toLowerCase()] = headers[key];
669
+ }
670
+ const get = (name) => {
671
+ const v = normalised[name.toLowerCase()];
672
+ return Array.isArray(v) ? v[0] : v;
673
+ };
674
+ const ua = (_a = get("user-agent")) != null ? _a : "";
675
+ const architecture = unquote(get("sec-ch-ua-arch"));
676
+ const bitness = unquote(get("sec-ch-ua-bitness"));
677
+ const model = unquote(get("sec-ch-ua-model"));
678
+ const platformVersion = unquote(get("sec-ch-ua-platform-version"));
679
+ const platform = (_b = unquote(get("sec-ch-ua-platform"))) != null ? _b : "";
680
+ const highEntropyData = {};
681
+ if (architecture !== void 0) highEntropyData.architecture = architecture;
682
+ if (bitness !== void 0) highEntropyData.bitness = bitness;
683
+ if (model !== void 0) highEntropyData.model = model;
684
+ if (platformVersion !== void 0) highEntropyData.platformVersion = platformVersion;
685
+ const isMobile = get("sec-ch-ua-mobile") === "?1";
686
+ const windowsVersion = platform === "Windows" ? deriveWindowsVersion2(platformVersion) : null;
687
+ const ctx = {
688
+ userAgent: ua,
689
+ platform,
690
+ language: "",
691
+ maxTouchPoints: isMobile ? 1 : 0,
692
+ highEntropyData: Object.keys(highEntropyData).length > 0 ? highEntropyData : void 0,
693
+ windowsVersion
694
+ };
695
+ return parseUA(ua, { ctx });
696
+ }
697
+
498
698
  // src/index.ts
499
699
  var { version: VERSION } = package_default;
500
- var isWebview = (ua) => /; wv/.test(ua);
501
700
  var isWechatMiniapp = () => {
502
701
  try {
503
702
  return typeof __wxjs_environment !== "undefined" && __wxjs_environment === "miniprogram";
@@ -515,6 +714,6 @@ uaBrowser.getLanguage = () => getLanguage(getNavContext());
515
714
  uaBrowser.VERSION = VERSION;
516
715
  var src_default = uaBrowser;
517
716
 
518
- export { VERSION, src_default as default, detectArch, detectBot, detectHeadless, getLanguage, getNavContext, getWindowsVersion, isWebview, isWechatMiniapp, parseUA };
717
+ export { ACCEPT_CH, VERSION, src_default as default, detectArch, detectBot, detectHeadless, getEnvContext, getLanguage, getNavContext, getWindowsVersion, isWebview, isWechatMiniapp, parseHeaders, parseUA };
519
718
  //# sourceMappingURL=index.mjs.map
520
719
  //# sourceMappingURL=index.mjs.map