
检测浏览器信息,具体代码:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>getBrowserInfo() --- Documentation</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=DM+Mono:wght@300;400;500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: #faf9f6;
--surface: #ffffff;
--surface-alt: #f3f1ec;
--border: #e4e0d8;
--border-faint: #ece9e2;
--accent: #1a6b3c;
--accent-light: #1a6b3c14;
--accent-mid: #1a6b3c30;
--warn: #b44d00;
--info: #1a5fb4;
--info-light: #1a5fb412;
--purple: #7c3aed;
--text-primary: #1a1a18;
--text-secondary: #6b6860;
--text-muted: #9e9a90;
--code-bg: #1b1b1f;
--code-text: #e0ddd6;
--code-key: #7eb8ff;
--code-string: #6dd4a0;
--code-bool: #ffb86c;
--code-comment: #5c5a54;
--code-tag: #ff7eb6;
--code-attr: #c9a0dc;
--code-punct: #888478;
--radius: 8px;
--radius-lg: 14px;
--font-serif: 'Instrument Serif', Georgia, serif;
--font-mono: 'DM Mono', 'Menlo', monospace;
--font-sans: 'Inter', -apple-system, sans-serif;
--sidebar-w: 260px;
--header-h: 64px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: calc(var(--header-h) + 24px); }
body {
background: var(--bg);
color: var(--text-primary);
font-family: var(--font-sans);
font-size: 15px;
line-height: 1.75;
-webkit-font-smoothing: antialiased;
}
::selection { background: var(--accent-mid); }
/* ─── Top Bar ─── */
.topbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-h);
background: rgba(250,249,246,0.88);
backdrop-filter: blur(16px) saturate(1.4);
-webkit-backdrop-filter: blur(16px) saturate(1.4);
border-bottom: 1px solid var(--border-faint);
z-index: 100;
display: flex;
align-items: center;
padding: 0 32px;
gap: 24px;
}
.topbar__logo {
font-family: var(--font-mono);
font-size: 14px;
font-weight: 500;
color: var(--accent);
white-space: nowrap;
text-decoration: none;
display: flex;
align-items: center;
gap: 10px;
}
.topbar__logo-icon {
width: 28px;
height: 28px;
background: var(--accent);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 13px;
font-weight: 600;
}
.topbar__sep {
width: 1px;
height: 20px;
background: var(--border);
}
.topbar__version {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-muted);
background: var(--surface-alt);
padding: 3px 10px;
border-radius: 100px;
border: 1px solid var(--border-faint);
}
.topbar__nav {
display: flex;
gap: 4px;
margin-left: auto;
}
.topbar__nav a {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
text-decoration: none;
padding: 6px 14px;
border-radius: 6px;
transition: all 0.2s ease;
}
.topbar__nav a:hover { color: var(--text-primary); background: var(--surface-alt); }
.topbar__nav a.active { color: var(--accent); background: var(--accent-light); }
/* ─── Sidebar ─── */
.sidebar {
position: fixed;
top: var(--header-h);
left: 0;
width: var(--sidebar-w);
height: calc(100vh - var(--header-h));
overflow-y: auto;
padding: 28px 20px 40px;
border-right: 1px solid var(--border-faint);
z-index: 50;
background: var(--bg);
}
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.sidebar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.sidebar__group {
margin-bottom: 28px;
}
.sidebar__group-title {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 10px;
padding-left: 12px;
}
.sidebar__link {
display: block;
font-size: 13px;
font-weight: 400;
color: var(--text-secondary);
text-decoration: none;
padding: 6px 12px;
border-radius: 6px;
transition: all 0.15s ease;
line-height: 1.5;
}
.sidebar__link:hover { color: var(--text-primary); background: var(--surface-alt); }
.sidebar__link.active {
color: var(--accent);
background: var(--accent-light);
font-weight: 500;
}
/* ─── Main Content ─── */
.main {
margin-left: var(--sidebar-w);
padding: calc(var(--header-h) + 40px) 56px 100px;
max-width: calc(var(--sidebar-w) + 820px);
}
@media (max-width: 1000px) {
.sidebar { display: none; }
.main { margin-left: 0; padding: calc(var(--header-h) + 24px) 20px 80px; }
}
/* ─── Typography ─── */
h1 {
font-family: var(--font-serif);
font-weight: 400;
font-size: clamp(36px, 4vw, 52px);
line-height: 1.1;
letter-spacing: -0.025em;
margin-bottom: 12px;
}
h1 em {
font-style: italic;
color: var(--accent);
}
.subtitle {
font-size: 17px;
font-weight: 400;
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 40px;
max-width: 560px;
}
h2 {
font-family: var(--font-serif);
font-weight: 400;
font-size: 30px;
line-height: 1.2;
letter-spacing: -0.02em;
margin-top: 56px;
margin-bottom: 16px;
padding-top: 24px;
border-top: 1px solid var(--border-faint);
scroll-margin-top: calc(var(--header-h) + 24px);
}
h2:first-of-type { border-top: none; padding-top: 0; margin-top: 0; }
h3 {
font-family: var(--font-sans);
font-weight: 600;
font-size: 17px;
margin-top: 36px;
margin-bottom: 10px;
color: var(--text-primary);
}
p { margin-bottom: 14px; color: var(--text-secondary); }
strong { color: var(--text-primary); font-weight: 600; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* ─── Code Blocks ─── */
.code-block {
position: relative;
background: var(--code-bg);
border-radius: var(--radius-lg);
padding: 24px 28px;
margin: 18px 0 24px;
overflow-x: auto;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.8;
color: var(--code-text);
border: 1px solid #2a2a30;
}
.code-block__lang {
position: absolute;
top: 10px;
right: 14px;
font-size: 10px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #555;
pointer-events: none;
}
.code-block__copy {
position: absolute;
top: 8px;
right: 60px;
font-family: var(--font-mono);
font-size: 11px;
padding: 4px 10px;
border-radius: 5px;
border: 1px solid #333;
background: transparent;
color: #666;
cursor: pointer;
transition: all 0.2s;
opacity: 0;
}
.code-block:hover .code-block__copy { opacity: 1; }
.code-block__copy:hover { color: var(--code-text); border-color: #555; }
.code-block__copy.copied { color: var(--code-string); border-color: var(--code-string); }
code {
font-family: var(--font-mono);
font-size: 13px;
background: var(--surface-alt);
border: 1px solid var(--border-faint);
padding: 2px 7px;
border-radius: 5px;
color: var(--accent);
}
.code-block code {
background: transparent;
border: none;
padding: 0;
color: inherit;
font-size: inherit;
}
/* Syntax colors */
.ck { color: var(--code-key); }
.cs { color: var(--code-string); }
.cb { color: var(--code-bool); }
.cc { color: var(--code-comment); font-style: italic; }
.ct { color: var(--code-tag); }
.ca { color: var(--code-attr); }
.cp { color: var(--code-punct); }
.cn { color: #ff9f43; }
.cf { color: #c9a0dc; }
/* ─── Tables ─── */
.table-wrap {
overflow-x: auto;
margin: 16px 0 24px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
thead th {
text-align: left;
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
padding: 12px 18px;
background: var(--surface-alt);
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
tbody td {
padding: 10px 18px;
border-bottom: 1px solid var(--border-faint);
color: var(--text-secondary);
vertical-align: top;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-alt); }
td code {
font-size: 12px;
}
/* ─── Info / Warning Boxes ─── */
.callout {
display: flex;
gap: 14px;
padding: 18px 22px;
border-radius: var(--radius);
margin: 18px 0;
font-size: 14px;
line-height: 1.7;
}
.callout__icon {
font-size: 16px;
flex-shrink: 0;
margin-top: 2px;
}
.callout--info {
background: var(--info-light);
border: 1px solid #1a5fb422;
color: var(--info);
}
.callout--warn {
background: #b44d0010;
border: 1px solid #b44d0020;
color: var(--warn);
}
.callout--tip {
background: var(--accent-light);
border: 1px solid var(--accent-mid);
color: var(--accent);
}
/* ─── Feature Grid ─── */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 14px;
margin: 20px 0 28px;
}
.feature-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
transition: all 0.2s ease;
}
.feature-card:hover {
border-color: var(--accent-mid);
box-shadow: 0 4px 16px rgba(0,0,0,0.04);
}
.feature-card__icon {
width: 36px;
height: 36px;
background: var(--accent-light);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
margin-bottom: 12px;
}
.feature-card__title {
font-weight: 600;
font-size: 14px;
margin-bottom: 6px;
color: var(--text-primary);
}
.feature-card__desc {
font-size: 13px;
color: var(--text-muted);
line-height: 1.6;
}
/* ─── Divider ─── */
hr {
border: none;
border-top: 1px solid var(--border-faint);
margin: 48px 0;
}
/* ─── Badge ─── */
.badge {
display: inline-block;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.06em;
padding: 3px 10px;
border-radius: 100px;
vertical-align: middle;
margin-left: 8px;
}
.badge--app {
background: var(--accent-light);
color: var(--accent);
border: 1px solid var(--accent-mid);
}
.badge--browser {
background: var(--info-light);
color: var(--info);
border: 1px solid #1a5fb422;
}
.badge--new {
background: #7c3aed12;
color: var(--purple);
border: 1px solid #7c3aed22;
}
/* ─── Param list ─── */
.param-list {
margin: 14px 0 24px;
}
.param-item {
display: grid;
grid-template-columns: 160px 1fr;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid var(--border-faint);
font-size: 14px;
align-items: baseline;
}
.param-item:last-child { border-bottom: none; }
.param-item__name {
font-family: var(--font-mono);
font-size: 13px;
color: var(--accent);
font-weight: 500;
}
.param-item__type {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
margin-left: 6px;
}
.param-item__desc {
color: var(--text-secondary);
}
/* ─── Footer ─── */
.doc-footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--border-faint);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
font-size: 12px;
color: var(--text-muted);
}
/* ─── UL ─── */
ul, ol {
padding-left: 24px;
margin-bottom: 14px;
color: var(--text-secondary);
}
li { margin-bottom: 6px; }
/* ─── Animations ─── */
.fade-in {
opacity: 0;
transform: translateY(12px);
animation: fadeIn 0.5s ease forwards;
}
@keyframes fadeIn {
to { opacity: 1; transform: translateY(0); }
}
.fade-in:nth-child(2) { animation-delay: 0.05s; }
.fade-in:nth-child(3) { animation-delay: 0.1s; }
.fade-in:nth-child(4) { animation-delay: 0.15s; }
/* ─── Scrollbar ─── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
</style>
</head>
<body>
<!-- ═══════════════ Top Bar ═══════════════ -->
<div class="topbar">
<a href="#" class="topbar__logo">
<span class="topbar__logo-icon">B</span>
getBrowserInfo
</a>
<span class="topbar__sep"></span>
<span class="topbar__version">v1.0.0</span>
<nav class="topbar__nav">
<a href="#overview" class="active">Docs</a>
<a href="#api">API</a>
<a href="#registry">Registry</a>
</nav>
</div>
<!-- ═══════════════ Sidebar ═══════════════ -->
<aside class="sidebar">
<div class="sidebar__group">
<div class="sidebar__group-title">Getting Started</div>
<a href="#overview" class="sidebar__link active">Overview</a>
<a href="#installation" class="sidebar__link">Installation</a>
<a href="#quick-start" class="sidebar__link">Quick Start</a>
</div>
<div class="sidebar__group">
<div class="sidebar__group-title">Core API</div>
<a href="#api" class="sidebar__link">getBrowserInfo()</a>
<a href="#return-value" class="sidebar__link">Return Value</a>
<a href="#properties" class="sidebar__link">Property Reference</a>
</div>
<div class="sidebar__group">
<div class="sidebar__group-title">Shortcut Methods</div>
<a href="#app-detection" class="sidebar__link">App Detection</a>
<a href="#platform-detection" class="sidebar__link">Platform Detection</a>
</div>
<div class="sidebar__group">
<div class="sidebar__group-title">Detection Engine</div>
<a href="#registry" class="sidebar__link">Registry (30+ engines)</a>
<a href="#platforms" class="sidebar__link">Platforms</a>
<a href="#devices" class="sidebar__link">Device Types</a>
<a href="#network" class="sidebar__link">Network Detection</a>
</div>
<div class="sidebar__group">
<div class="sidebar__group-title">Advanced</div>
<a href="#server-side" class="sidebar__link">Server-Side Usage</a>
<a href="#custom-registry" class="sidebar__link">Extending the Registry</a>
<a href="#best-practices" class="sidebar__link">Best Practices</a>
<a href="#limitations" class="sidebar__link">Limitations</a>
</div>
</aside>
<!-- ═══════════════ Main Content ═══════════════ -->
<main class="main">
<!-- ── Overview ── -->
<section id="overview" class="fade-in">
<h1>getBrowserInfo<em>()</em></h1>
<p class="subtitle">
A zero-dependency browser and App WebView detection library for the Chinese and global ecosystem.
Identifies 30+ App WebViews and standalone browsers, detects platform, device type, and network conditions --- in both client and server environments.
</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-card__icon">🔍</div>
<div class="feature-card__title">30+ Engines</div>
<div class="feature-card__desc">WeChat, Alipay, DingTalk, Douyin, Taobao, QQ, Weibo, Facebook, Instagram and more</div>
</div>
<div class="feature-card">
<div class="feature-card__icon">🌐</div>
<div class="feature-card__title">Cross-Platform</div>
<div class="feature-card__desc">Android, iOS, Windows, macOS, Linux --- with Mobile / Tablet / PC device detection</div>
</div>
<div class="feature-card">
<div class="feature-card__icon">⚡</div>
<div class="feature-card__title">Zero Dependencies</div>
<div class="feature-card__desc">Pure JavaScript, UMD module --- works with ES modules, CommonJS, and script tags</div>
</div>
<div class="feature-card">
<div class="feature-card__icon">🖥️</div>
<div class="feature-card__title">Server-Side Ready</div>
<div class="feature-card__desc">Pass a custom UA string for server-side detection in Node.js, Deno, or any runtime</div>
</div>
</div>
</section>
<!-- ── Installation ── -->
<section id="installation">
<h2>Installation</h2>
<h3>Via npm</h3>
<div class="code-block">
<span class="code-block__lang">sh</span>
<pre><code>npm install get-browser-info</code></pre>
</div>
<h3>Via CDN (script tag)</h3>
<div class="code-block">
<span class="code-block__lang">html</span>
<pre><code><span class="ct"><script</span> <span class="ca">src</span>=<span class="cs">"https://unpkg.com/get-browser-info/dist/index.min.js"</span><span class="ct">></script></span>
<span class="cc"><!-- Exposes window.getBrowserInfo globally --></span></code></pre>
</div>
<h3>Direct Copy</h3>
<p>Since the library has <strong>zero dependencies</strong>, you can also copy the source file directly into your project. It self-registers via UMD and works in any module system.</p>
</section>
<!-- ── Quick Start ── -->
<section id="quick-start">
<h2>Quick Start</h2>
<h3>Client-Side (auto-detect current environment)</h3>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">const</span> info = <span class="cf">getBrowserInfo</span>();
console.<span class="cf">log</span>(info.name); <span class="cc">// "WeChat"</span>
console.<span class="cf">log</span>(info.version); <span class="cc">// "8.0.31"</span>
console.<span class="cf">log</span>(info.type); <span class="cc">// "app"</span>
console.<span class="cf">log</span>(info.inApp); <span class="cc">// "WeChat"</span>
console.<span class="cf">log</span>(info.platform); <span class="cc">// "Android"</span>
console.<span class="cf">log</span>(info.device); <span class="cc">// "Mobile"</span>
console.<span class="cf">log</span>(info.isWebView); <span class="cc">// true</span>
console.<span class="cf">log</span>(info.network); <span class="cc">// "4g"</span></code></pre>
</div>
<h3>Server-Side (pass a custom UA)</h3>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">const</span> getBrowserInfo = <span class="cf">require</span>(<span class="cs">'get-browser-info'</span>);
<span class="cc">// In Express / Koa / Fastify middleware:</span>
app.<span class="cf">use</span>((<span class="ca">req</span>, <span class="ca">res</span>, <span class="ca">next</span>) <span class="ck">=></span> {
req.browser = <span class="cf">getBrowserInfo</span>(req.headers[<span class="cs">'user-agent'</span>]);
<span class="cf">next</span>();
});</code></pre>
</div>
<h3>Quick Boolean Checks</h3>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">if</span> (getBrowserInfo.<span class="cf">isWeChat</span>()) {
<span class="cc">// Redirect to WeChat-specific payment flow</span>
<span class="cf">initWeChatPay</span>();
}
<span class="ck">if</span> (getBrowserInfo.<span class="cf">isMobile</span>()) {
<span class="cc">// Serve mobile-optimized assets</span>
}</code></pre>
</div>
</section>
<hr>
<!-- ── API ── -->
<section id="api">
<h2>getBrowserInfo([customUA])</h2>
<p>The main detection function. When called with no arguments, it reads <code>navigator.userAgent</code> from the current browser. Pass a UA string for server-side or testing use.</p>
<h3>Parameters</h3>
<div class="param-list">
<div class="param-item">
<div>
<span class="param-item__name">customUA</span>
<span class="param-item__type">string?</span>
</div>
<div class="param-item__desc">
Optional. A User-Agent string to analyze. If omitted or falsy, uses <code>navigator.userAgent</code>.
When a custom UA is provided, the <code>network</code> field is returned as an empty string (network info requires a live browser environment).
</div>
</div>
</div>
<h3>Returns</h3>
<p>An <code>Object</code> containing the detection result (see <a href="#return-value">Return Value</a> below).</p>
<h3 id="return-value">Return Value</h3>
<div class="code-block">
<span class="code-block__lang">ts</span>
<pre><code>{
<span class="ck">name</span>: <span class="cs">string</span>, <span class="cc">// Browser / WebView name</span>
<span class="ck">version</span>: <span class="cs">string</span>, <span class="cc">// Version number</span>
<span class="ck">type</span>: <span class="cs">"app" | "browser" | "unknown"</span>,
<span class="ck">inApp</span>: <span class="cs">string | null</span>, <span class="cc">// Host app name (only for type="app")</span>
<span class="ck">platform</span>: <span class="cs">string</span>, <span class="cc">// "Android" | "iOS" | "Windows" | "macOS" | "Linux" | "Unknown"</span>
<span class="ck">device</span>: <span class="cs">string</span>, <span class="cc">// "Mobile" | "Tablet" | "PC"</span>
<span class="ck">network</span>: <span class="cs">string</span>, <span class="cc">// "4g" | "3g" | "2g" | "slow-2g" | "wifi" | ""</span>
<span class="ck">isWebView</span>: <span class="cs">boolean</span>, <span class="cc">// true if type === "app"</span>
<span class="ck">ua</span>: <span class="cs">string</span>, <span class="cc">// The original UA string used for detection</span>
}</code></pre>
</div>
</section>
<!-- ── Properties ── -->
<section id="properties">
<h2>Property Reference</h2>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>name</code></td>
<td><code>string</code></td>
<td>The detected browser or WebView name, e.g. <code>"WeChat"</code>, <code>"Chrome"</code>, <code>"Safari"</code>. Returns <code>"Unknown"</code> if no registry entry matches.</td>
</tr>
<tr>
<td><code>version</code></td>
<td><code>string</code></td>
<td>The version extracted from the UA. Returns an empty string <code>""</code> if extraction fails or the engine has no version regex.</td>
</tr>
<tr>
<td><code>type</code></td>
<td><code>"app" | "browser" | "unknown"</code></td>
<td><code>"app"</code> for in-app WebViews, <code>"browser"</code> for standalone browsers, <code>"unknown"</code> if nothing matched.</td>
</tr>
<tr>
<td><code>inApp</code></td>
<td><code>string | null</code></td>
<td>The host application name when inside a WebView (e.g. <code>"WeChat"</code>, <code>"Douyin"</code>). <code>null</code> for standalone browsers.</td>
</tr>
<tr>
<td><code>platform</code></td>
<td><code>string</code></td>
<td>Operating system: <code>"Android"</code>, <code>"iOS"</code>, <code>"Windows"</code>, <code>"macOS"</code>, <code>"Linux"</code>, or <code>"Unknown"</code>.</td>
</tr>
<tr>
<td><code>device</code></td>
<td><code>string</code></td>
<td>Device form factor: <code>"Mobile"</code>, <code>"Tablet"</code>, or <code>"PC"</code>. Defaults to <code>"PC"</code> when no mobile/tablet indicators are found.</td>
</tr>
<tr>
<td><code>network</code></td>
<td><code>string</code></td>
<td>Network type from the <a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation" target="_blank" rel="noopener">Network Information API</a>. Values: <code>"4g"</code>, <code>"3g"</code>, <code>"2g"</code>, <code>"slow-2g"</code>, <code>"wifi"</code>. Returns <code>""</code> when using a custom UA or if the API is unavailable.</td>
</tr>
<tr>
<td><code>isWebView</code></td>
<td><code>boolean</code></td>
<td>Convenience flag. <code>true</code> when <code>type === "app"</code>.</td>
</tr>
<tr>
<td><code>ua</code></td>
<td><code>string</code></td>
<td>The raw User-Agent string used for detection. Useful for logging or further analysis.</td>
</tr>
</tbody>
</table>
</div>
</section>
<hr>
<!-- ── Shortcut Methods ── -->
<section id="app-detection">
<h2>Shortcut Methods --- App Detection</h2>
<p>All shortcuts accept an optional <code>ua</code> parameter. If omitted, the current browser's UA is used.</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Method</th>
<th>Signature</th>
<th>Detects</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>isWeChat(ua?)</code></td>
<td><code>boolean</code></td>
<td>WeChat / 微信内置浏览器</td>
</tr>
<tr>
<td><code>isAlipay(ua?)</code></td>
<td><code>boolean</code></td>
<td>Alipay / 支付宝内置浏览器</td>
</tr>
<tr>
<td><code>isQQ(ua?)</code></td>
<td><code>boolean</code></td>
<td>QQ App 内置浏览器</td>
</tr>
<tr>
<td><code>isWeibo(ua?)</code></td>
<td><code>boolean</code></td>
<td>Weibo / 微博内置浏览器</td>
</tr>
<tr>
<td><code>isDingTalk(ua?)</code></td>
<td><code>boolean</code></td>
<td>DingTalk / 钉钉内置浏览器</td>
</tr>
<tr>
<td><code>isDouyin(ua?)</code></td>
<td><code>boolean</code></td>
<td>Douyin / 抖音内置浏览器</td>
</tr>
</tbody>
</table>
</div>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="cc">// Client-side --- no arguments needed</span>
<span class="ck">if</span> (getBrowserInfo.<span class="cf">isWeChat</span>()) {
<span class="cf">showWeChatGuide</span>();
}
<span class="cc">// Server-side --- pass the UA string</span>
<span class="ck">const</span> ua = req.headers[<span class="cs">'user-agent'</span>];
<span class="ck">if</span> (getBrowserInfo.<span class="cf">isAlipay</span>(ua)) {
<span class="cf">redirect</span>(<span class="cs">'/alipay-callback'</span>);
}</code></pre>
</div>
</section>
<!-- ── Platform Detection ── -->
<section id="platform-detection">
<h2>Shortcut Methods --- Platform Detection</h2>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Method</th>
<th>Signature</th>
<th>Detects</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>isMobile(ua?)</code></td>
<td><code>boolean</code></td>
<td>Any mobile device (Android phone, iPhone, iPad, iPod)</td>
</tr>
<tr>
<td><code>isAndroid(ua?)</code></td>
<td><code>boolean</code></td>
<td>Android platform (phones + tablets)</td>
</tr>
<tr>
<td><code>isIOS(ua?)</code></td>
<td><code>boolean</code></td>
<td>iOS platform (iPhone, iPad, iPod)</td>
</tr>
</tbody>
</table>
</div>
</section>
<hr>
<!-- ── Registry ── -->
<section id="registry">
<h2>Detection Registry</h2>
<p>The engine uses a priority-ordered registry --- the <strong>first match wins</strong>. App WebViews are checked before standalone browsers, because many App WebViews inherit the underlying browser engine's UA tokens.</p>
<h3>App WebViews <span class="badge badge--app">type: app</span></h3>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Host App</th>
<th>UA Test Pattern</th>
<th>Version Pattern</th>
</tr>
</thead>
<tbody>
<tr><td><code>WeChat</code></td><td>WeChat</td><td><code>MicroMessenger</code></td><td><code>MicroMessenger/x.x.x</code></td></tr>
<tr><td><code>Alipay</code></td><td>Alipay</td><td><code>AlipayClient</code></td><td><code>AlipayClient/x.x.x</code></td></tr>
<tr><td><code>DingTalk</code></td><td>DingTalk</td><td><code>DingTalk</code></td><td><code>DingTalk/x.x.x</code></td></tr>
<tr><td><code>Taobao</code></td><td>Taobao</td><td><code>AliApp(TB/</code></td><td><code>AliApp(TB/x.x.x</code></td></tr>
<tr><td><code>Tmall</code></td><td>Tmall</td><td><code>AliApp(TM/</code></td><td><code>AliApp(TM/x.x.x</code></td></tr>
<tr><td><code>Douyin</code></td><td>Douyin</td><td><code>aweme</code></td><td><code>aweme/x.x.x</code></td></tr>
<tr><td><code>BytedanceWV</code></td><td>Bytedance</td><td><code>BytedanceWebview</code></td><td><code>BytedanceWebview/x.x.x</code></td></tr>
<tr><td><code>Toutiao</code></td><td>Toutiao</td><td><code>NewsArticle</code></td><td><code>NewsArticle/x.x.x</code></td></tr>
<tr><td><code>Weibo</code></td><td>Weibo</td><td><code>Weibo</code></td><td><code>Weibo_x.x.x</code></td></tr>
<tr><td><code>BaiduApp</code></td><td>Baidu</td><td><code>baiduboxapp</code></td><td><code>baiduboxapp/x.x.x</code></td></tr>
<tr><td><code>QQ</code></td><td>QQ</td><td><code>QQ/</code></td><td><code>QQ/x.x.x</code></td></tr>
<tr><td><code>MQQBrowser</code></td><td>QQ</td><td><code>MQQBrowser</code></td><td><code>MQQBrowser/x.x.x</code></td></tr>
<tr><td><code>KakaoTalk</code></td><td>KakaoTalk</td><td><code>KAKAOTALK</code></td><td><code>KAKAOTALK_x.x.x</code></td></tr>
<tr><td><code>Facebook</code></td><td>Facebook</td><td><code>FBAN|FBAV</code></td><td><code>FBAV/x.x.x</code></td></tr>
<tr><td><code>Instagram</code></td><td>Instagram</td><td><code>Instagram</code></td><td><code>Instagram x.x.x</code></td></tr>
<tr><td><code>Line</code></td><td>Line</td><td><code>Line/</code></td><td><code>Line/x.x.x</code></td></tr>
</tbody>
</table>
</div>
<h3>Standalone Browsers <span class="badge badge--browser">type: browser</span></h3>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Name</th>
<th>Category</th>
<th>UA Test Pattern</th>
<th>Version Pattern</th>
</tr>
</thead>
<tbody>
<tr><td><code>Edge</code></td><td>Mainstream</td><td><code>Edg/</code></td><td><code>Edg/x.x.x</code></td></tr>
<tr><td><code>Opera</code></td><td>Mainstream</td><td><code>OPR/</code></td><td><code>OPR/x.x.x</code></td></tr>
<tr><td><code>Vivaldi</code></td><td>Mainstream</td><td><code>Vivaldi</code></td><td><code>Vivaldi/x.x.x</code></td></tr>
<tr><td><code>Brave</code></td><td>Mainstream</td><td><code>Brave</code></td><td><code>Brave/x.x.x</code></td></tr>
<tr><td><code>Yandex</code></td><td>Mainstream</td><td><code>Yandex</code></td><td><code>YaBrowser/x.x.x</code></td></tr>
<tr><td><code>HuaweiBrowser</code></td><td>Manufacturer</td><td><code>HuaweiBrowser</code></td><td><code>HuaweiBrowser/x.x.x</code></td></tr>
<tr><td><code>MiuiBrowser</code></td><td>Manufacturer</td><td><code>MiuiBrowser</code></td><td><code>MiuiBrowser/x.x.x</code></td></tr>
<tr><td><code>SamsungBrowser</code></td><td>Manufacturer</td><td><code>SamsungBrowser</code></td><td><code>SamsungBrowser/x.x.x</code></td></tr>
<tr><td><code>UCBrowser</code></td><td>Regional</td><td><code>UCBrowser</code></td><td><code>UCBrowser/x.x.x</code></td></tr>
<tr><td><code>QQBrowser</code></td><td>Regional</td><td><code>QQBrowser</code></td><td><code>QQBrowser/x.x.x</code></td></tr>
<tr><td><code>BaiduBrowser</code></td><td>Regional</td><td><code>BIDUBrowser</code></td><td><code>BIDUBrowser x.x.x</code></td></tr>
<tr><td><code>SogouBrowser</code></td><td>Regional</td><td><code>MetaSr|Sogou</code></td><td><code>SogouMobileBrowser/x.x.x</code></td></tr>
<tr><td><code>Quark</code></td><td>Regional</td><td><code>Quark</code></td><td><code>Quark/x.x.x</code></td></tr>
<tr><td><code>360Browser</code></td><td>Regional</td><td><code>360Browser|QihooBrowser</code></td><td><code>360Browser/x.x.x</code></td></tr>
<tr><td><code>Firefox</code></td><td>Mainstream</td><td><code>Firefox</code></td><td><code>Firefox/x.x.x</code></td></tr>
<tr><td><code>Chrome</code></td><td>Mainstream</td><td><code>Chrome</code></td><td><code>Chrome/x.x.x</code></td></tr>
<tr><td><code>Safari</code></td><td>Mainstream</td><td><code>Safari</code></td><td><code>Version/x.x.x</code></td></tr>
<tr><td><code>IE</code></td><td>Legacy</td><td><code>Trident|MSIE</code></td><td><code>rv:x.x.x</code> or <code>MSIE x.x</code></td></tr>
</tbody>
</table>
</div>
<div class="callout callout--warn">
<span class="callout__icon">⚠</span>
<div><strong>Priority matters.</strong> Safari and Chrome are placed near the bottom of the registry because many App WebViews include <code>Safari</code> and <code>Chrome</code> tokens in their UA. The specific App patterns are matched first to avoid false positives.</div>
</div>
</section>
<!-- ── Platforms ── -->
<section id="platforms">
<h2>Platform Detection</h2>
<p>Platform detection checks the UA against known OS identifiers. Priority order:</p>
<div class="table-wrap">
<table>
<thead>
<tr><th>#</th><th>Platform</th><th>UA Pattern</th></tr>
</thead>
<tbody>
<tr><td>1</td><td><code>Android</code></td><td><code>/Android/i</code></td></tr>
<tr><td>2</td><td><code>iOS</code></td><td><code>/iPhone|iPad|iPod/i</code></td></tr>
<tr><td>3</td><td><code>Windows</code></td><td><code>/Windows/i</code></td></tr>
<tr><td>4</td><td><code>macOS</code></td><td><code>/Mac OS X/i</code></td></tr>
<tr><td>5</td><td><code>Linux</code></td><td><code>/Linux/i</code></td></tr>
</tbody>
</table>
</div>
</section>
<!-- ── Devices ── -->
<section id="devices">
<h2>Device Type Detection</h2>
<p>Device detection uses a three-tier heuristic:</p>
<div class="table-wrap">
<table>
<thead>
<tr><th>#</th><th>Device</th><th>UA Pattern</th><th>Notes</th></tr>
</thead>
<tbody>
<tr><td>1</td><td><code>Tablet</code></td><td><code>/iPad/i</code></td><td>iPad is always tablet</td></tr>
<tr><td>2</td><td><code>Tablet</code></td><td><code>/Android(?!.*Mobile)/i</code></td><td>Android without "Mobile" token</td></tr>
<tr><td>3</td><td><code>Mobile</code></td><td><code>/iPhone|iPod|Android.*Mobile/i</code></td><td>iPhone, iPod, or Android with "Mobile"</td></tr>
</tbody>
</table>
</div>
<div class="callout callout--info">
<span class="callout__icon">ℹ</span>
<div>If no device pattern matches (e.g., on desktop Windows/macOS), the fallback is <code>"PC"</code>.</div>
</div>
</section>
<!-- ── Network ── -->
<section id="network">
<h2>Network Detection</h2>
<p>When running in a browser environment with no custom UA, the library queries the <a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation" target="_blank" rel="noopener">Network Information API</a> to detect the effective connection type.</p>
<div class="table-wrap">
<table>
<thead>
<tr><th>Value</th><th>Meaning</th></tr>
</thead>
<tbody>
<tr><td><code>"slow-2g"</code></td><td>RTT ≥ 2000ms, downlink < 50 Kbps</td></tr>
<tr><td><code>"2g"</code></td><td>RTT ≥ 1400ms, downlink < 70 Kbps</td></tr>
<tr><td><code>"3g"</code></td><td>RTT ≥ 270ms, downlink < 700 Kbps</td></tr>
<tr><td><code>"4g"</code></td><td>RTT < 100ms, downlink varies</td></tr>
<tr><td><code>"wifi"</code></td><td>Wi-Fi connection (via <code>conn.type</code>)</td></tr>
<tr><td><code>""</code></td><td>API unavailable or using custom UA</td></tr>
</tbody>
</table>
</div>
</section>
<hr>
<!-- ── Server-Side ── -->
<section id="server-side">
<h2>Server-Side Usage</h2>
<h3>Node.js / Express</h3>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">const</span> express = <span class="cf">require</span>(<span class="cs">'express'</span>);
<span class="ck">const</span> getBrowserInfo = <span class="cf">require</span>(<span class="cs">'get-browser-info'</span>);
<span class="ck">const</span> app = <span class="cf">express</span>();
<span class="cc">// Attach browser info to every request</span>
app.<span class="cf">use</span>((<span class="ca">req</span>, <span class="ca">res</span>, <span class="ca">next</span>) <span class="ck">=></span> {
req.browserInfo = <span class="cf">getBrowserInfo</span>(req.headers[<span class="cs">'user-agent'</span>]);
<span class="cf">next</span>();
});
app.<span class="cf">get</span>(<span class="cs">'/download'</span>, (<span class="ca">req</span>, <span class="ca">res</span>) <span class="ck">=></span> {
<span class="ck">const</span> { platform, device, isWebView } = req.browserInfo;
<span class="ck">if</span> (isWebView) {
<span class="ck">return</span> res.<span class="cf">json</span>({
message: <span class="cs">'Please open in your default browser'</span>,
deepLink: platform === <span class="cs">'iOS'</span>
? <span class="cs">'https://apps.apple.com/app/xxx'</span>
: <span class="cs">'https://play.google.com/store/apps/xxx'</span>
});
}
<span class="cc">// Serve platform-specific download</span>
<span class="ck">const</span> file = platform === <span class="cs">'iOS'</span> ? <span class="cs">'app.ipa'</span> : <span class="cs">'app.apk'</span>;
res.<span class="cf">download</span>(file);
});</code></pre>
</div>
<h3>Koa</h3>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">const</span> Koa = <span class="cf">require</span>(<span class="cs">'koa'</span>);
<span class="ck">const</span> getBrowserInfo = <span class="cf">require</span>(<span class="cs">'get-browser-info'</span>);
<span class="ck">const</span> app = <span class="ck">new</span> <span class="cf">Koa</span>();
app.<span class="cf">use</span>((<span class="ca">ctx</span>) <span class="ck">=></span> {
<span class="ck">const</span> info = <span class="cf">getBrowserInfo</span>(ctx.headers[<span class="cs">'user-agent'</span>]);
<span class="cc">// Redirect WeChat users to a custom page</span>
<span class="ck">if</span> (info.inApp === <span class="cs">'WeChat'</span>) {
ctx.<span class="cf">redirect</span>(<span class="cs">'/wechat-guide'</span>);
}
});</code></pre>
</div>
<h3>Logging & Analytics</h3>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="cc">// Collect browser stats for analytics</span>
<span class="ck">const</span> info = <span class="cf">getBrowserInfo</span>(ua);
analytics.<span class="cf">track</span>(<span class="cs">'page_view'</span>, {
browser: info.name,
version: info.version,
platform: info.platform,
device: info.device,
isWebView: info.isWebView,
hostApp: info.inApp,
});</code></pre>
</div>
</section>
<!-- ── Custom Registry ── -->
<section id="custom-registry">
<h2>Extending the Registry</h2>
<p>The current architecture uses an internal <code>REGISTRY</code> array that isn't directly exported. If you need to add custom detections, the recommended approach is to fork the source and append entries to the registry:</p>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="cc">// Inside the REGISTRY array, add your custom entry:</span>
{
<span class="ck">test</span>: <span class="cs">/MyCustomApp/i</span>,
<span class="ck">name</span>: <span class="cs">'MyCustomApp'</span>,
<span class="ck">version</span>: <span class="cs">/MyCustomApp\/([\d.]+)/</span>,
<span class="ck">type</span>: <span class="cs">'app'</span>,
<span class="ck">inApp</span>: <span class="cs">'MyCustomApp'</span>,
}</code></pre>
</div>
<h3>Registry Entry Schema</h3>
<div class="table-wrap">
<table>
<thead>
<tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
</thead>
<tbody>
<tr>
<td><code>test</code></td>
<td><code>RegExp</code></td>
<td>Yes</td>
<td>Matches against the UA string. No capture groups needed.</td>
</tr>
<tr>
<td><code>name</code></td>
<td><code>string</code></td>
<td>Yes</td>
<td>The display name returned in <code>info.name</code>.</td>
</tr>
<tr>
<td><code>version</code></td>
<td><code>RegExp</code></td>
<td>No</td>
<td>Must contain exactly one capture group for the version number.</td>
</tr>
<tr>
<td><code>type</code></td>
<td><code>"app" | "browser"</code></td>
<td>No</td>
<td>Defaults to <code>"browser"</code>. Set to <code>"app"</code> for WebViews.</td>
</tr>
<tr>
<td><code>inApp</code></td>
<td><code>string</code></td>
<td>No</td>
<td>The host application name. Only meaningful when <code>type === "app"</code>.</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- ── Best Practices ── -->
<section id="best-practices">
<h2>Best Practices</h2>
<h3>1. Check <code>isWebView</code> before JS SDKs</h3>
<p>Many third-party JS SDKs (payment, social sharing, analytics) behave differently inside WebViews. Gate them with the detection result:</p>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">const</span> info = <span class="cf">getBrowserInfo</span>();
<span class="ck">if</span> (info.isWebView) {
<span class="cc">// Use native bridge or App-specific SDK</span>
<span class="cf">initNativeBridge</span>(info.inApp);
} <span class="ck">else</span> {
<span class="cc">// Use standard web SDKs</span>
<span class="cf">initWebSDK</span>();
}</code></pre>
</div>
<h3>2. Use shortcuts for common checks</h3>
<p>For single-app checks, shortcut methods are cleaner and more readable than comparing <code>info.name</code>:</p>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="cc">// Preferred</span>
<span class="ck">if</span> (getBrowserInfo.<span class="cf">isWeChat</span>()) { ... }
<span class="cc">// Also works, but less idiomatic</span>
<span class="ck">if</span> (<span class="cf">getBrowserInfo</span>().name === <span class="cs">'WeChat'</span>) { ... }</code></pre>
</div>
<h3>3. Cache the result</h3>
<p>The detection is deterministic within a page session. Avoid calling <code>getBrowserInfo()</code> repeatedly in hot paths:</p>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="cc">// Good --- compute once, use everywhere</span>
<span class="ck">const</span> browserInfo = <span class="cf">getBrowserInfo</span>();
<span class="cc">// Avoid --- re-parses UA on every call</span>
<span class="ck">for</span> (<span class="ck">const</span> item <span class="ck">of</span> list) {
<span class="ck">if</span> (<span class="cf">getBrowserInfo</span>().isWebView) { ... }
}</code></pre>
</div>
<h3>4. Combine with feature detection</h3>
<p>UA detection tells you <em>what the browser claims to be</em>, not <em>what it supports</em>. Use it for routing and UX decisions, but pair it with feature detection for capability gating:</p>
<div class="code-block">
<span class="code-block__lang">js</span>
<pre><code><span class="ck">const</span> info = <span class="cf">getBrowserInfo</span>();
<span class="cc">// UA detection for routing</span>
<span class="ck">if</span> (info.inApp === <span class="cs">'WeChat'</span>) {
<span class="cf">loadWeChatJSSDK</span>();
}
<span class="cc">// Feature detection for capabilities</span>
<span class="ck">if</span> (<span class="cs">'IntersectionObserver'</span> <span class="ck">in</span> window) {
<span class="cf">enableLazyLoading</span>();
}</code></pre>
</div>
</section>
<!-- ── Limitations ── -->
<section id="limitations">
<h2>Limitations</h2>
<div class="callout callout--warn">
<span class="callout__icon">⚠</span>
<div>
<strong>User-Agent strings are self-reported and can be spoofed.</strong>
Browsers and apps can modify their UA at will. This library performs <em>best-effort</em> detection based on common UA patterns --- it is not a security mechanism.
</div>
</div>
<ul>
<li><strong>UA Client Hints:</strong> Chrome is gradually reducing UA string granularity in favor of <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent" target="_blank" rel="noopener">User-Agent Client Hints</a>. This library relies on the full UA string and does not yet support Client Hints APIs.</li>
<li><strong>Embedded browsers in new apps:</strong> As new apps launch with their own WebViews, the registry needs to be updated. Contributions are welcome.</li>
<li><strong>Version extraction edge cases:</strong> Some UAs embed version numbers in non-standard formats. If <code>version</code> returns <code>""</code>, the UA likely doesn't match the expected format.</li>
<li><strong>iPadOS 13+:</strong> iPadOS 13+ reports as macOS in its UA by default. The library may classify it as <code>macOS</code> / <code>PC</code> unless the UA includes the <code>iPad</code> token.</li>
<li><strong>Network detection:</strong> The Network Information API is not available in all browsers (notably limited in Safari and Firefox). When unavailable, <code>network</code> returns <code>""</code>.</li>
</ul>
</section>
<hr>
<!-- ── License ── -->
<div class="doc-footer">
<span>MIT License · getBrowserInfo v1.0.0</span>
<span>Last updated: July 2025</span>
</div>
</main>
<script>
// Sidebar active state on scroll
const sections = document.querySelectorAll('section[id]');
const sidebarLinks = document.querySelectorAll('.sidebar__link');
function updateActiveLink() {
let current = '';
sections.forEach(section => {
const top = section.offsetTop - 100;
if (window.scrollY >= top) {
current = section.getAttribute('id');
}
});
sidebarLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + current) {
link.classList.add('active');
}
});
}
window.addEventListener('scroll', updateActiveLink, { passive: true });
updateActiveLink();
// Copy button for code blocks
document.querySelectorAll('.code-block').forEach(block => {
const btn = document.createElement('button');
btn.className = 'code-block__copy';
btn.textContent = 'Copy';
btn.addEventListener('click', () => {
const code = block.querySelector('pre').textContent;
navigator.clipboard.writeText(code).then(() => {
btn.textContent = 'Copied!';
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = 'Copy';
btn.classList.remove('copied');
}, 2000);
});
});
block.appendChild(btn);
});
// Fade in on scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, { threshold: 0.1 });
document.querySelectorAll('h2, .table-wrap, .code-block, .callout, .feature-grid, .param-list, ul, p').forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(14px)';
el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
observer.observe(el);
});
</script>
</body>
</html>
JS工具如下:
javascript
/**
* 浏览器 / App WebView 检测工具
*
* 功能:
* - 识别主流国内 App 内置 WebView(微信、支付宝、QQ、钉钉、淘宝、抖音等)
* - 识别主流独立浏览器(Chrome、Safari、Firefox、Edge 等)
* - 识别设备厂商浏览器(华为、小米、三星、UC 等)
* - 检测平台(Android / iOS / Windows / macOS / Linux)
* - 检测设备类型(Mobile / Tablet / PC)
* - 检测网络类型(4g / 3g / 2g / slow-2g)
* - 支持传入自定义 UA,可运行于服务端
*
* 用法:
* const info = getBrowserInfo(); // 客户端自动取 navigator.userAgent
* const info = getBrowserInfo(uaStr); // 传入自定义 UA(服务端场景)
* getBrowserInfo.isWeChat(); // 快捷判断
*/
;
(function(root, factory) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory();
} else {
root.getBrowserInfo = factory();
}
})(typeof globalThis !== 'undefined' ? globalThis : this, function() {
// ============================================
// 浏览器 / App 注册表
// ============================================
//
// 每一项结构:
// test --- test(ua) 的正则,无需捕获组
// name --- 浏览器 / App 名称
// version --- 提取版本号的正则,需带一个捕获组
// type --- 'app' | 'browser',默认 'browser'
// inApp --- 宿主 App 名称,仅 type='app' 时有效
//
// ⚠️ 顺序决定优先级:先匹配的先返回
var REGISTRY = [
// ────────────────────────────────────────
// App 内置 WebView(优先级最高)
// ────────────────────────────────────────
{ test: /MicroMessenger/i, name: 'WeChat', version: /MicroMessenger\/([\d.]+)/, type: 'app', inApp: 'WeChat' },
{ test: /AlipayClient/i, name: 'Alipay', version: /AlipayClient\/([\d.]+)/, type: 'app', inApp: 'Alipay' },
{ test: /DingTalk/i, name: 'DingTalk', version: /DingTalk\/([\d.]+)/, type: 'app', inApp: 'DingTalk' },
{ test: /AliApp\(TB\//i, name: 'Taobao', version: /AliApp\(TB\/([\d.]+)/, type: 'app', inApp: 'Taobao' },
{ test: /AliApp\(TM\//i, name: 'Tmall', version: /AliApp\(TM\/([\d.]+)/, type: 'app', inApp: 'Tmall' },
{ test: /aweme/i, name: 'Douyin', version: /aweme\/([\d.]+)/, type: 'app', inApp: 'Douyin' },
{ test: /BytedanceWebview/i, name: 'BytedanceWV', version: /BytedanceWebview\/([\d.]+)/, type: 'app',
inApp: 'Bytedance' },
{ test: /NewsArticle/i, name: 'Toutiao', version: /NewsArticle\/([\d.]+)/, type: 'app', inApp: 'Toutiao' },
{ test: /Weibo/i, name: 'Weibo', version: /Weibo[_/]([\d.]+)/, type: 'app', inApp: 'Weibo' },
{ test: /baiduboxapp/i, name: 'BaiduApp', version: /baiduboxapp\/([\d.]+)/, type: 'app', inApp: 'Baidu' },
{ test: /QQ\//i, name: 'QQ', version: /QQ\/([\d.]+)/, type: 'app', inApp: 'QQ' },
{ test: /MQQBrowser/i, name: 'MQQBrowser', version: /MQQBrowser\/([\d.]+)/, type: 'app', inApp: 'QQ' },
{ test: /KAKAOTALK/i, name: 'KakaoTalk', version: /KAKAOTALK[_/]([\d.]+)/, type: 'app', inApp: 'KakaoTalk' },
{ test: /FBAN|FBAV/i, name: 'Facebook', version: /FBAV\/([\d.]+)/, type: 'app', inApp: 'Facebook' },
{ test: /Instagram/i, name: 'Instagram', version: /Instagram\s([\d.]+)/, type: 'app', inApp: 'Instagram' },
{ test: /Line\//i, name: 'Line', version: /Line\/([\d.]+)/, type: 'app', inApp: 'Line' },
// ────────────────────────────────────────
// 独立浏览器(从特殊到通用)
// ────────────────────────────────────────
{ test: /Edg\//i, name: 'Edge', version: /Edg\/([\d.]+)/ },
{ test: /OPR\//i, name: 'Opera', version: /OPR\/([\d.]+)/ },
{ test: /Vivaldi/i, name: 'Vivaldi', version: /Vivaldi\/([\d.]+)/ },
{ test: /Brave/i, name: 'Brave', version: /Brave\/([\d.]+)/ },
{ test: /Yandex/i, name: 'Yandex', version: /YaBrowser\/([\d.]+)/ },
{ test: /HuaweiBrowser/i, name: 'HuaweiBrowser', version: /HuaweiBrowser\/([\d.]+)/ },
{ test: /MiuiBrowser/i, name: 'MiuiBrowser', version: /MiuiBrowser\/([\d.]+)/ },
{ test: /SamsungBrowser/i, name: 'SamsungBrowser', version: /SamsungBrowser\/([\d.]+)/ },
{ test: /UCBrowser/i, name: 'UCBrowser', version: /UCBrowser\/([\d.]+)/ },
{ test: /QQBrowser/i, name: 'QQBrowser', version: /QQBrowser\/([\d.]+)/ },
{ test: /BIDUBrowser/i, name: 'BaiduBrowser', version: /BIDUBrowser[\s/]([\d.]+)/ },
{ test: /MetaSr|Sogou/i, name: 'SogouBrowser', version: /SogouMobileBrowser\/([\d.]+)/ },
{ test: /Quark/i, name: 'Quark', version: /Quark\/([\d.]+)/ },
{ test: /360Browser|QihooBrowser/i, name: '360Browser', version: /(?:360Browser|QihooBrowser)\/([\d.]+)/ },
{ test: /Firefox/i, name: 'Firefox', version: /Firefox\/([\d.]+)/ },
{ test: /Chrome/i, name: 'Chrome', version: /Chrome\/([\d.]+)/ },
{ test: /Safari/i, name: 'Safari', version: /Version\/([\d.]+)/ },
{ test: /Trident|MSIE/i, name: 'IE', version: /(?:rv:|MSIE\s?)([\d.]+)/ },
];
// 平台映射表
var PLATFORM_MAP = [
{ test: /Android/i, name: 'Android' },
{ test: /iPhone|iPad|iPod/i, name: 'iOS' },
{ test: /Windows/i, name: 'Windows' },
{ test: /Mac OS X/i, name: 'macOS' },
{ test: /Linux/i, name: 'Linux' },
];
// 设备类型映射表
var DEVICE_MAP = [
{ test: /iPad/i, name: 'Tablet' },
{ test: /Android(?!.*Mobile)/i, name: 'Tablet' },
{ test: /iPhone|iPod|Android.*Mobile/i, name: 'Mobile' },
];
// ============================================
// 内部工具函数
// ============================================
/**
* 在映射表中查找第一个匹配项
* @param {Array} map - 映射表
* @param {string} ua - UA 字符串
* @returns {Object|null}
*/
function findMatch(map, ua) {
for (var i = 0; i < map.length; i++) {
if (map[i].test.test(ua)) {
return map[i];
}
}
return null;
}
/**
* 用正则从 UA 中提取版本号
* @param {string} ua - UA 字符串
* @param {RegExp} regex - 带一个捕获组的正则
* @returns {string}
*/
function extractVersion(ua, regex) {
if (!regex) return '';
var m = ua.match(regex);
return m ? m[1] : '';
}
/**
* 获取当前网络类型(仅客户端可用)
* @returns {string} '4g' | '3g' | '2g' | 'slow-2g' | 'wifi' | ''
*/
function getNetworkType() {
if (typeof navigator === 'undefined') return '';
var conn = navigator.connection ||
navigator.mozConnection ||
navigator.webkitConnection;
if (!conn) return '';
return conn.effectiveType || conn.type || '';
}
// ============================================
// 主函数
// ============================================
/**
* 获取当前浏览器 / App WebView 信息
*
* @param {string} [customUA] 自定义 UA 字符串(服务端场景传入)
* @return {Object} 浏览器信息对象
*
* @property {string} name - 浏览器 / WebView 名称
* @property {string} version - 版本号
* @property {string} type - 'app' | 'browser' | 'unknown'
* @property {string|null} inApp - 宿主 App 名称(仅 type='app' 时有值)
* @property {string} platform - 'Android' | 'iOS' | 'Windows' | 'macOS' | 'Linux' | 'Unknown'
* @property {string} device - 'Mobile' | 'Tablet' | 'PC'
* @property {string} network - '4g' | '3g' | '2g' | 'slow-2g' | ''
* @property {boolean} isWebView - 是否为 App 内嵌 WebView
* @property {string} ua - 原始 UA 字符串
*/
function getBrowserInfo(customUA) {
var ua = customUA || (typeof navigator !== 'undefined' ? navigator.userAgent : '');
var matched = findMatch(REGISTRY, ua);
var platform = findMatch(PLATFORM_MAP, ua);
var device = findMatch(DEVICE_MAP, ua);
return {
name: matched ? matched.name : 'Unknown',
version: matched ? extractVersion(ua, matched.version) : '',
type: matched ? (matched.type || 'browser') : 'unknown',
inApp: matched && matched.inApp ? matched.inApp : null,
platform: platform ? platform.name : 'Unknown',
device: device ? device.name : 'PC',
network: customUA ? '' : getNetworkType(),
isWebView: !!(matched && matched.type === 'app'),
ua: ua,
};
}
// ============================================
// 便捷快捷方法
// ============================================
/** 是否为微信内置浏览器 */
getBrowserInfo.isWeChat = function(ua) {
return /MicroMessenger/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为支付宝内置浏览器 */
getBrowserInfo.isAlipay = function(ua) {
return /AlipayClient/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为 QQ 内置浏览器 */
getBrowserInfo.isQQ = function(ua) {
return /QQ\//i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为微博内置浏览器 */
getBrowserInfo.isWeibo = function(ua) {
return /Weibo/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为钉钉内置浏览器 */
getBrowserInfo.isDingTalk = function(ua) {
return /DingTalk/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为抖音内置浏览器 */
getBrowserInfo.isDouyin = function(ua) {
return /aweme/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为移动端设备 */
getBrowserInfo.isMobile = function(ua) {
ua = ua || (typeof navigator !== 'undefined' ? navigator.userAgent : '');
return /Android|iPhone|iPad|iPod/i.test(ua);
};
/** 是否为 Android 设备 */
getBrowserInfo.isAndroid = function(ua) {
return /Android/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
/** 是否为 iOS 设备 */
getBrowserInfo.isIOS = function(ua) {
return /iPhone|iPad|iPod/i.test(ua || (typeof navigator !== 'undefined' ? navigator.userAgent : ''));
};
return getBrowserInfo;
});