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/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var package_default = {
3
- version: "1.0.1"};
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
@@ -193,23 +196,31 @@ var OS_DEFS = [
193
196
  { name: "Symbian", detect: /Symbian/, versionPattern: null },
194
197
  { name: "MeeGo", detect: /MeeGo/, versionPattern: null },
195
198
  { name: "BlackBerry", detect: /(BlackBerry|RIM)/, versionPattern: null },
196
- { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ },
197
199
  { name: "FreeBSD", detect: /FreeBSD/, versionPattern: null },
198
200
  { name: "Debian", detect: /Debian/, versionPattern: /Debian\/([\d.]+)/ },
199
201
  { name: "Ubuntu", detect: /Ubuntu/, versionPattern: null },
200
- { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
202
+ // Linux must come before Chrome OS: Chrome OS UAs contain "X11", so Linux matches first,
203
+ // then Chrome OS overrides it.
201
204
  { name: "Linux", detect: /(Linux|X11)/, versionPattern: null },
205
+ { name: "Chrome OS", detect: /CrOS/, versionPattern: null },
202
206
  { name: "Tizen", detect: /Tizen/, versionPattern: /Tizen ([\d.]+)/ },
203
207
  { name: "iOS", detect: /like Mac OS X/, versionPattern: /OS ([\d_]+) like/ },
204
208
  { name: "MacOS", detect: /Macintosh/, versionPattern: /Mac OS X -?([\d_.]+)/ },
209
+ { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
210
+ // HarmonyOS must come after Android: HarmonyOS UAs include "Android", so Android matches
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.
205
213
  {
206
214
  name: "HarmonyOS",
207
215
  detect: /HarmonyOS/,
208
- versionPattern: /Android ([\d.]+)[;)]/,
209
- versionLookup: { "10": "2" }
216
+ versionPattern: [/HarmonyOS[\s/]([\d.]+)/, /Android ([\d.]+)[;)]/],
217
+ versionLookup: { "10": "2", "11": "3", "12": "3", "13": "4" }
210
218
  },
211
- { name: "Android", detect: /(Android|Adr)/, versionPattern: /(?:Android|Adr) ([\d.]+)/ },
219
+ // OpenHarmony (open-source base) must come after HarmonyOS to override any earlier match.
220
+ { name: "OpenHarmony", detect: /OpenHarmony/, versionPattern: /OpenHarmony[\s/]([\d.]+)/ },
212
221
  { name: "KaiOS", detect: /KAIOS/, versionPattern: /KAIOS\/([\d.]+)/ },
222
+ // Windows must come before Windows Phone: Windows Phone UAs contain "Windows", so Windows
223
+ // matches first, then Windows Phone overrides it.
213
224
  {
214
225
  name: "Windows",
215
226
  detect: /Windows/,
@@ -225,7 +236,8 @@ var OS_DEFS = [
225
236
  "5.1": "XP",
226
237
  "5.0": "2000"
227
238
  }
228
- }
239
+ },
240
+ { name: "Windows Phone", detect: /(IEMobile|Windows Phone)/, versionPattern: /Windows Phone(?: OS)? ([\d.]+)/ }
229
241
  ];
230
242
 
231
243
  // src/detectors/os.ts
@@ -239,7 +251,7 @@ function detectOs(ua, windowsVersion) {
239
251
  if (!matchedDef) return { os: "unknown", osVersion: "unknown" };
240
252
  let osVersion = "unknown";
241
253
  if (matchedDef.versionPattern) {
242
- const raw = extractVersion(ua, matchedDef.versionPattern);
254
+ const raw = Array.isArray(matchedDef.versionPattern) ? extractVersionFromPatterns(ua, matchedDef.versionPattern) : extractVersion(ua, matchedDef.versionPattern);
243
255
  if (raw !== null) {
244
256
  const normalised = raw.replace(/_/g, ".");
245
257
  if (matchedDef.versionLookup) {
@@ -249,6 +261,8 @@ function detectOs(ua, windowsVersion) {
249
261
  } else if (matchedDef.name === "Windows") {
250
262
  const n = parseInt(normalised, 10);
251
263
  osVersion = isNaN(n) ? "unknown" : n.toString();
264
+ } else {
265
+ osVersion = normalised;
252
266
  }
253
267
  } else {
254
268
  osVersion = normalised;
@@ -340,7 +354,33 @@ var ARCH_DEFS = [
340
354
  ];
341
355
 
342
356
  // src/detectors/arch.ts
343
- 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
+ }
344
384
  for (const def of ARCH_DEFS) {
345
385
  if (def.detect.test(ua)) {
346
386
  return def.name;
@@ -355,6 +395,13 @@ function detectHeadless(ua) {
355
395
  return HEADLESS_PATTERN.test(ua);
356
396
  }
357
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
+
358
405
  // src/utils/navigator.ts
359
406
  function getNavContext() {
360
407
  if (typeof navigator === "undefined") {
@@ -379,16 +426,19 @@ function getLanguage(nav) {
379
426
 
380
427
  // src/parse.ts
381
428
  function parseUA(ua, options = {}) {
382
- var _a, _b, _c, _d, _e;
383
- 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;
384
432
  const { browser: rawBrowser, version: rawVersion } = detectBrowser(ua);
385
- const { os, osVersion } = detectOs(ua, windowsVersion);
386
- const device = detectDevice(ua, nav);
387
- const arch = detectArch(ua);
433
+ const { os, osVersion: rawOsVersion } = detectOs(ua, effectiveWindowsVersion);
434
+ let osVersion = rawOsVersion;
435
+ const device = detectDevice(ua, effectiveNav);
436
+ const arch = detectArch(ua, options.ctx);
437
+ const nav = effectiveNav;
388
438
  const { isBot, botName } = detectBot(ua);
389
439
  const isHeadless = detectHeadless(ua);
390
440
  const language = nav ? getLanguage(nav) : "unknown";
391
- 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";
392
442
  let browser = rawBrowser;
393
443
  let version = rawVersion;
394
444
  if (nav) {
@@ -410,7 +460,7 @@ function parseUA(ua, options = {}) {
410
460
  }
411
461
  }
412
462
  if (is360) {
413
- const saveDataEnabled = ((_b = nav.connection) == null ? void 0 : _b.saveData) === true;
463
+ const saveDataEnabled = ((_e = nav.connection) == null ? void 0 : _e.saveData) === true;
414
464
  if (getMimeType(nav, "application/gameplugin") || !saveDataEnabled) {
415
465
  browser = "360SE";
416
466
  } else {
@@ -423,8 +473,8 @@ function parseUA(ua, options = {}) {
423
473
  }
424
474
  if (browser === "Baidu" && /(Opera|OPR|OPT)/.test(ua)) {
425
475
  browser = "Opera";
426
- const opVer = (_d = (_c = /OPR\/([\d.]+)/.exec(ua)) != null ? _c : /OPT\/([\d.]+)/.exec(ua)) != null ? _d : /Opera\/([\d.]+)/.exec(ua);
427
- 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";
428
478
  }
429
479
  if (browser === "Chrome" && /\S+Browser\//.test(ua)) {
430
480
  const m = /(\S+Browser)\/([\d.]+)/.exec(ua);
@@ -449,6 +499,12 @@ function parseUA(ua, options = {}) {
449
499
  } catch (e) {
450
500
  }
451
501
  }
502
+ if (os === "iOS" && browser === "Safari") {
503
+ const m = /Version\/([\d.]+)/.exec(ua);
504
+ if (m && parseInt(m[1], 10) > parseInt(osVersion, 10)) {
505
+ osVersion = m[1];
506
+ }
507
+ }
452
508
  const engine = detectEngine(ua, browser, version);
453
509
  return {
454
510
  browser,
@@ -458,7 +514,7 @@ function parseUA(ua, options = {}) {
458
514
  osVersion,
459
515
  device,
460
516
  arch,
461
- isWebview: /; wv/.test(ua),
517
+ isWebview: isWebview(ua),
462
518
  isHeadless,
463
519
  isBot,
464
520
  botName,
@@ -482,9 +538,165 @@ async function getWindowsVersion(nav) {
482
538
  }
483
539
  }
484
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
+
485
698
  // src/index.ts
486
699
  var { version: VERSION } = package_default;
487
- var isWebview = (ua) => /; wv/.test(ua);
488
700
  var isWechatMiniapp = () => {
489
701
  try {
490
702
  return typeof __wxjs_environment !== "undefined" && __wxjs_environment === "miniprogram";
@@ -502,6 +714,6 @@ uaBrowser.getLanguage = () => getLanguage(getNavContext());
502
714
  uaBrowser.VERSION = VERSION;
503
715
  var src_default = uaBrowser;
504
716
 
505
- 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 };
506
718
  //# sourceMappingURL=index.mjs.map
507
719
  //# sourceMappingURL=index.mjs.map