api-ape 3.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -570
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -202
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +32 -7
- package/server/README.md +287 -53
- package/server/adapters/README.md +28 -19
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +332 -27
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -221
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -225
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -308
- package/server/lib/broadcast.js +0 -146
|
@@ -1,286 +1,348 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Robust User-Agent Parser
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview Robust User-Agent Parser - Zero Dependencies
|
|
3
|
+
*
|
|
4
|
+
* This module provides a comprehensive User-Agent string parser that extracts
|
|
5
|
+
* detailed information about the client's browser, operating system, device,
|
|
6
|
+
* CPU architecture, and bot status. It has zero external dependencies and
|
|
7
|
+
* handles a wide variety of user agents including:
|
|
8
|
+
*
|
|
9
|
+
* - Modern browsers (Chrome, Firefox, Safari, Edge, Opera, Brave, etc.)
|
|
10
|
+
* - Mobile browsers (iOS Safari, Chrome Mobile, Samsung Internet, etc.)
|
|
11
|
+
* - AI bots (ChatGPT, Claude, GPTBot, Perplexity, etc.)
|
|
12
|
+
* - Traditional crawlers (Googlebot, Bingbot, etc.)
|
|
13
|
+
* - Headless browsers (HeadlessChrome, PhantomJS, Puppeteer, etc.)
|
|
14
|
+
* - In-app browsers (Facebook, Instagram, WhatsApp, etc.)
|
|
15
|
+
* - Game consoles (PlayStation, Xbox, Nintendo)
|
|
16
|
+
* - Smart TVs and set-top boxes
|
|
17
|
+
*
|
|
18
|
+
* The parser is designed to be:
|
|
19
|
+
* - **Fast**: Simple regex matching with early termination
|
|
20
|
+
* - **Accurate**: Handles edge cases and common variations
|
|
21
|
+
* - **Safe**: Returns null values instead of throwing errors
|
|
22
|
+
* - **Comprehensive**: Extracts browser, engine, OS, device, and CPU info
|
|
23
|
+
*
|
|
24
|
+
* @module server/utils/parseUserAgent
|
|
25
|
+
* @see {@link module:server/utils/userAgent/patterns} - Pattern definitions
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const parseUserAgent = require('./parseUserAgent')
|
|
29
|
+
*
|
|
30
|
+
* const result = parseUserAgent(
|
|
31
|
+
* 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
|
|
32
|
+
* '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
33
|
+
* )
|
|
34
|
+
*
|
|
35
|
+
* console.log(result)
|
|
36
|
+
* // {
|
|
37
|
+
* // browser: { name: 'Chrome', version: '120.0.0.0', major: '120' },
|
|
38
|
+
* // engine: { name: 'Blink', version: '120.0.0.0' },
|
|
39
|
+
* // os: { name: 'Windows', version: '10' },
|
|
40
|
+
* // device: { type: null, vendor: null, model: null },
|
|
41
|
+
* // cpu: { architecture: 'amd64' },
|
|
42
|
+
* // isBot: false,
|
|
43
|
+
* // raw: '...'
|
|
44
|
+
* // }
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Detect AI bots
|
|
48
|
+
* const result = parseUserAgent('ClaudeBot/1.0')
|
|
49
|
+
* console.log(result.browser.name) // 'ClaudeBot'
|
|
50
|
+
* console.log(result.isBot) // true
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Handle mobile devices
|
|
54
|
+
* const result = parseUserAgent(
|
|
55
|
+
* 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ...'
|
|
56
|
+
* )
|
|
57
|
+
* console.log(result.device.type) // 'mobile'
|
|
58
|
+
* console.log(result.device.vendor) // 'Apple'
|
|
59
|
+
* console.log(result.os.name) // 'iOS'
|
|
5
60
|
*/
|
|
6
61
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{ name: 'PerplexityBot', pattern: /PerplexityBot\/?([\d.]*)/i },
|
|
18
|
-
{ name: 'Perplexity-User', pattern: /Perplexity-User\/?([\d.]*)/i },
|
|
19
|
-
{ name: 'Google-Extended', pattern: /Google-Extended/i },
|
|
62
|
+
const {
|
|
63
|
+
BROWSERS,
|
|
64
|
+
OS_PATTERNS,
|
|
65
|
+
ENGINES,
|
|
66
|
+
DEVICE_PATTERNS,
|
|
67
|
+
DEVICE_VENDORS,
|
|
68
|
+
CPU_PATTERNS,
|
|
69
|
+
BOT_PATTERNS,
|
|
70
|
+
MODEL_PATTERNS,
|
|
71
|
+
} = require("./userAgent/patterns");
|
|
20
72
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{ name: 'wget', pattern: /Wget\/?([\d.]*)/i },
|
|
30
|
-
{ name: 'HeadlessChrome', pattern: /HeadlessChrome\/?([\d.]*)/i },
|
|
31
|
-
{ name: 'PhantomJS', pattern: /PhantomJS\/?([\d.]*)/i },
|
|
32
|
-
{ name: 'Puppeteer', pattern: /Puppeteer/i },
|
|
33
|
-
{ name: 'Playwright', pattern: /Playwright/i },
|
|
34
|
-
|
|
35
|
-
// WebViews / In-app browsers (before generic browsers)
|
|
36
|
-
{ name: 'Facebook', pattern: /\bFB[\w_]*\/?([\d.]*)/i },
|
|
37
|
-
{ name: 'Instagram', pattern: /Instagram\s?([\d.]*)/i },
|
|
38
|
-
{ name: 'Twitter', pattern: /Twitter/i },
|
|
39
|
-
{ name: 'TikTok', pattern: /TikTok/i },
|
|
40
|
-
{ name: 'Snapchat', pattern: /Snapchat/i },
|
|
41
|
-
{ name: 'LinkedIn', pattern: /LinkedInApp/i },
|
|
42
|
-
{ name: 'Pinterest', pattern: /Pinterest/i },
|
|
43
|
-
{ name: 'WhatsApp', pattern: /WhatsApp\/?([\d.]*)/i },
|
|
44
|
-
{ name: 'Telegram', pattern: /TelegramBot/i },
|
|
45
|
-
|
|
46
|
-
// Chromium-based (before Chrome)
|
|
47
|
-
{ name: 'Edge', pattern: /Edg(?:e|A|iOS)?\/?([\d.]*)/i },
|
|
48
|
-
{ name: 'Opera', pattern: /(?:OPR|Opera)\/?([\d.]*)/i },
|
|
49
|
-
{ name: 'Brave', pattern: /Brave\/?([\d.]*)/i },
|
|
50
|
-
{ name: 'Vivaldi', pattern: /Vivaldi\/?([\d.]*)/i },
|
|
51
|
-
{ name: 'Yandex', pattern: /YaBrowser\/?([\d.]*)/i },
|
|
52
|
-
{ name: 'Samsung Internet', pattern: /SamsungBrowser\/?([\d.]*)/i },
|
|
53
|
-
{ name: 'UC Browser', pattern: /UCBrowser\/?([\d.]*)/i },
|
|
54
|
-
{ name: 'QQ Browser', pattern: /QQBrowser\/?([\d.]*)/i },
|
|
55
|
-
{ name: 'Whale', pattern: /Whale\/?([\d.]*)/i },
|
|
56
|
-
|
|
57
|
-
// Major browsers
|
|
58
|
-
{ name: 'Chrome', pattern: /(?:Chrome|CriOS)\/?([\d.]*)/i },
|
|
59
|
-
{ name: 'Firefox', pattern: /(?:Firefox|FxiOS)\/?([\d.]*)/i },
|
|
60
|
-
{ name: 'Safari', pattern: /Version\/([\d.]*)\s.*Safari/i },
|
|
61
|
-
|
|
62
|
-
// Legacy IE
|
|
63
|
-
{ name: 'IE', pattern: /(?:MSIE\s|Trident.*rv:)([\d.]*)/i },
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
// OS detection patterns - ORDER MATTERS (specific before generic)
|
|
67
|
-
const OS_PATTERNS = [
|
|
68
|
-
{ name: 'iOS', pattern: /(?:iPhone|iPad|iPod).*?OS\s([\d_]+)/i, versionSep: '_' },
|
|
69
|
-
// Android - exclude "like Android" (e.g., Kindle UA)
|
|
70
|
-
{ name: 'Android', pattern: /(?<!like\s)Android\s?([\d.]*)/i },
|
|
71
|
-
{ name: 'macOS', pattern: /Mac OS X\s?([\d_\.]*)/i, versionSep: '_' },
|
|
72
|
-
{
|
|
73
|
-
name: 'Windows', pattern: /Windows NT\s?([\d.]*)/i, versionMap: {
|
|
74
|
-
'10.0': '10', '6.3': '8.1', '6.2': '8', '6.1': '7', '6.0': 'Vista', '5.1': 'XP'
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{ name: 'Chrome OS', pattern: /CrOS\s\w+\s([\d.]*)/i },
|
|
78
|
-
// Specific distros before generic Linux
|
|
79
|
-
{ name: 'Ubuntu', pattern: /Ubuntu/i },
|
|
80
|
-
{ name: 'Fedora', pattern: /Fedora/i },
|
|
81
|
-
{ name: 'FreeBSD', pattern: /FreeBSD/i },
|
|
82
|
-
{ name: 'Linux', pattern: /Linux/i },
|
|
83
|
-
];
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {Object} BrowserInfo
|
|
75
|
+
* Information about the detected browser.
|
|
76
|
+
*
|
|
77
|
+
* @property {string|null} name - Browser name (e.g., 'Chrome', 'Firefox', 'Safari')
|
|
78
|
+
* @property {string|null} version - Full version string (e.g., '120.0.0.0')
|
|
79
|
+
* @property {string|null} major - Major version number (e.g., '120')
|
|
80
|
+
*/
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
{ name: 'Presto', pattern: /Presto\/([\d.]+)/i },
|
|
93
|
-
];
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {Object} EngineInfo
|
|
84
|
+
* Information about the browser's rendering engine.
|
|
85
|
+
*
|
|
86
|
+
* @property {string|null} name - Engine name (e.g., 'Blink', 'Gecko', 'WebKit')
|
|
87
|
+
* @property {string|null} version - Engine version string
|
|
88
|
+
*/
|
|
94
89
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{ type: 'embedded', pattern: /Embedded/i },
|
|
103
|
-
];
|
|
90
|
+
/**
|
|
91
|
+
* @typedef {Object} OSInfo
|
|
92
|
+
* Information about the operating system.
|
|
93
|
+
*
|
|
94
|
+
* @property {string|null} name - OS name (e.g., 'Windows', 'macOS', 'iOS', 'Android')
|
|
95
|
+
* @property {string|null} version - OS version (e.g., '10', '14.0', '17.0')
|
|
96
|
+
*/
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{ vendor: 'OnePlus', pattern: /OnePlus|ONEPLUS/i },
|
|
114
|
-
{ vendor: 'LG', pattern: /LG[-;\/\s]/i },
|
|
115
|
-
// Sony: brand + Xperia + tablet codes (SGP)
|
|
116
|
-
{ vendor: 'Sony', pattern: /Sony|Xperia|PlayStation|\bSGP\d+\b/i },
|
|
117
|
-
{ vendor: 'Motorola', pattern: /Motorola|Moto\s|\bmoto\s/i },
|
|
118
|
-
{ vendor: 'HTC', pattern: /HTC/i },
|
|
119
|
-
{ vendor: 'Nokia', pattern: /Nokia/i },
|
|
120
|
-
{ vendor: 'Oppo', pattern: /OPPO/i },
|
|
121
|
-
{ vendor: 'Vivo', pattern: /vivo/i },
|
|
122
|
-
{ vendor: 'Realme', pattern: /RMX\d/i },
|
|
123
|
-
// Microsoft: Xbox, Surface, and "Microsoft;" in UA
|
|
124
|
-
{ vendor: 'Microsoft', pattern: /Xbox|Surface|Microsoft;/i },
|
|
125
|
-
{ vendor: 'Nintendo', pattern: /Nintendo/i },
|
|
126
|
-
];
|
|
98
|
+
/**
|
|
99
|
+
* @typedef {Object} DeviceInfo
|
|
100
|
+
* Information about the device.
|
|
101
|
+
*
|
|
102
|
+
* @property {string|null} type - Device type: 'mobile', 'tablet', 'console', 'smarttv', 'wearable', 'embedded', or null for desktop
|
|
103
|
+
* @property {string|null} vendor - Device manufacturer (e.g., 'Apple', 'Samsung', 'Google')
|
|
104
|
+
* @property {string|null} model - Device model (e.g., 'iPhone', 'Pixel 8 Pro', 'SM-G998B')
|
|
105
|
+
*/
|
|
127
106
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
];
|
|
107
|
+
/**
|
|
108
|
+
* @typedef {Object} CPUInfo
|
|
109
|
+
* Information about the CPU architecture.
|
|
110
|
+
*
|
|
111
|
+
* @property {string|null} architecture - CPU architecture: 'arm64', 'arm', 'amd64', 'ia32', or null
|
|
112
|
+
*/
|
|
135
113
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
];
|
|
114
|
+
/**
|
|
115
|
+
* @typedef {Object} ParsedUserAgent
|
|
116
|
+
* Complete result from parsing a User-Agent string.
|
|
117
|
+
*
|
|
118
|
+
* @property {BrowserInfo} browser - Browser information
|
|
119
|
+
* @property {EngineInfo} engine - Rendering engine information
|
|
120
|
+
* @property {OSInfo} os - Operating system information
|
|
121
|
+
* @property {DeviceInfo} device - Device information
|
|
122
|
+
* @property {CPUInfo} cpu - CPU architecture information
|
|
123
|
+
* @property {boolean} isBot - True if the user agent appears to be a bot/crawler
|
|
124
|
+
* @property {string|null} raw - The original User-Agent string
|
|
125
|
+
*/
|
|
149
126
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Creates an empty result object with all null values.
|
|
129
|
+
*
|
|
130
|
+
* Used as the default return value and as a base for building results.
|
|
131
|
+
*
|
|
132
|
+
* @private
|
|
133
|
+
* @function createEmptyResult
|
|
134
|
+
* @param {string|null} ua - The original User-Agent string to store in `raw`
|
|
135
|
+
* @returns {ParsedUserAgent} Empty result object with all null values
|
|
136
|
+
*/
|
|
137
|
+
function createEmptyResult(ua) {
|
|
138
|
+
return {
|
|
139
|
+
browser: { name: null, version: null, major: null },
|
|
140
|
+
engine: { name: null, version: null },
|
|
141
|
+
os: { name: null, version: null },
|
|
142
|
+
device: { type: null, vendor: null, model: null },
|
|
143
|
+
cpu: { architecture: null },
|
|
144
|
+
isBot: false,
|
|
145
|
+
raw: ua || null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
164
148
|
|
|
165
149
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
150
|
+
* Parses a User-Agent string and extracts detailed client information.
|
|
151
|
+
*
|
|
152
|
+
* This function analyzes the User-Agent string to determine:
|
|
153
|
+
* - **Browser**: Name, version, and major version number
|
|
154
|
+
* - **Engine**: Rendering engine (Blink, Gecko, WebKit, etc.)
|
|
155
|
+
* - **OS**: Operating system name and version
|
|
156
|
+
* - **Device**: Type (mobile/tablet/etc.), vendor, and model
|
|
157
|
+
* - **CPU**: Architecture (arm64, amd64, etc.)
|
|
158
|
+
* - **Bot status**: Whether this appears to be an automated client
|
|
159
|
+
*
|
|
160
|
+
* The parser handles many edge cases:
|
|
161
|
+
* - Safari detection when Chrome isn't present
|
|
162
|
+
* - Version number normalization (e.g., Windows NT versions)
|
|
163
|
+
* - Multiple bot detection patterns (AI bots, crawlers, headless browsers)
|
|
164
|
+
*
|
|
165
|
+
* @function parseUserAgent
|
|
166
|
+
* @param {string|null|undefined} ua - The User-Agent string to parse
|
|
167
|
+
* @returns {ParsedUserAgent} Parsed information about the client
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Parse a Chrome user agent
|
|
171
|
+
* const result = parseUserAgent(
|
|
172
|
+
* 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
|
|
173
|
+
* 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
174
|
+
* )
|
|
175
|
+
*
|
|
176
|
+
* console.log(result.browser.name) // 'Chrome'
|
|
177
|
+
* console.log(result.browser.major) // '120'
|
|
178
|
+
* console.log(result.os.name) // 'macOS'
|
|
179
|
+
* console.log(result.os.version) // '10.15.7'
|
|
180
|
+
* console.log(result.device.type) // null (desktop)
|
|
181
|
+
* console.log(result.isBot) // false
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* // Parse a mobile Safari user agent
|
|
185
|
+
* const result = parseUserAgent(
|
|
186
|
+
* 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ' +
|
|
187
|
+
* 'AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'
|
|
188
|
+
* )
|
|
189
|
+
*
|
|
190
|
+
* console.log(result.browser.name) // 'Safari'
|
|
191
|
+
* console.log(result.os.name) // 'iOS'
|
|
192
|
+
* console.log(result.device.type) // 'mobile'
|
|
193
|
+
* console.log(result.device.vendor) // 'Apple'
|
|
194
|
+
* console.log(result.device.model) // 'iPhone'
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* // Detect an AI bot
|
|
198
|
+
* const result = parseUserAgent('ChatGPT-User')
|
|
199
|
+
*
|
|
200
|
+
* console.log(result.browser.name) // 'ChatGPT-User'
|
|
201
|
+
* console.log(result.isBot) // true
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // Handle null/undefined input
|
|
205
|
+
* const result = parseUserAgent(null)
|
|
206
|
+
*
|
|
207
|
+
* console.log(result.browser.name) // null
|
|
208
|
+
* console.log(result.raw) // null
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* // Use in request handler
|
|
212
|
+
* app.use((req, res, next) => {
|
|
213
|
+
* req.userAgent = parseUserAgent(req.headers['user-agent'])
|
|
214
|
+
*
|
|
215
|
+
* // Block bots from certain endpoints
|
|
216
|
+
* if (req.userAgent.isBot && req.path.startsWith('/api/')) {
|
|
217
|
+
* return res.status(403).json({ error: 'Bots not allowed' })
|
|
218
|
+
* }
|
|
219
|
+
*
|
|
220
|
+
* // Serve mobile-optimized content
|
|
221
|
+
* if (req.userAgent.device.type === 'mobile') {
|
|
222
|
+
* req.isMobile = true
|
|
223
|
+
* }
|
|
224
|
+
*
|
|
225
|
+
* next()
|
|
226
|
+
* })
|
|
169
227
|
*/
|
|
170
228
|
function parseUserAgent(ua) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
229
|
+
// Handle null, undefined, or non-string input
|
|
230
|
+
if (ua == null || typeof ua !== "string") {
|
|
231
|
+
return createEmptyResult(ua);
|
|
232
|
+
}
|
|
175
233
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
engine: { name: null, version: null },
|
|
179
|
-
os: { name: null, version: null },
|
|
180
|
-
device: { type: null, vendor: null, model: null },
|
|
181
|
-
cpu: { architecture: null },
|
|
182
|
-
isBot: false,
|
|
183
|
-
raw: ua,
|
|
184
|
-
};
|
|
234
|
+
const result = createEmptyResult(ua);
|
|
235
|
+
result.raw = ua;
|
|
185
236
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
237
|
+
// =========================================================================
|
|
238
|
+
// BROWSER DETECTION
|
|
239
|
+
// =========================================================================
|
|
240
|
+
// Try each browser pattern in priority order (most specific first)
|
|
241
|
+
for (const { name, pattern } of BROWSERS) {
|
|
242
|
+
const match = ua.match(pattern);
|
|
243
|
+
if (match) {
|
|
244
|
+
result.browser.name = name;
|
|
245
|
+
result.browser.version = match[1] || null;
|
|
246
|
+
result.browser.major = match[1] ? match[1].split(".")[0] : null;
|
|
247
|
+
break;
|
|
195
248
|
}
|
|
249
|
+
}
|
|
196
250
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
251
|
+
// Special case: Safari detection when Chrome isn't present
|
|
252
|
+
// Many browsers include "Safari" in their UA, but real Safari doesn't have "Chrome"
|
|
253
|
+
if (!result.browser.name && /Safari/i.test(ua) && !/Chrome/i.test(ua)) {
|
|
254
|
+
result.browser.name = "Safari";
|
|
255
|
+
const m = ua.match(/Safari\/([\d.]+)/i);
|
|
256
|
+
result.browser.version = m ? m[1] : null;
|
|
257
|
+
result.browser.major = result.browser.version
|
|
258
|
+
? result.browser.version.split(".")[0]
|
|
259
|
+
: null;
|
|
260
|
+
}
|
|
204
261
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
262
|
+
// =========================================================================
|
|
263
|
+
// ENGINE DETECTION
|
|
264
|
+
// =========================================================================
|
|
265
|
+
for (const { name, pattern } of ENGINES) {
|
|
266
|
+
const match = ua.match(pattern);
|
|
267
|
+
if (match) {
|
|
268
|
+
result.engine.name = name;
|
|
269
|
+
result.engine.version = match[1] || null;
|
|
270
|
+
break;
|
|
213
271
|
}
|
|
272
|
+
}
|
|
214
273
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
if (version && versionMap && versionMap[version]) {
|
|
225
|
-
version = versionMap[version];
|
|
226
|
-
}
|
|
227
|
-
result.os.version = version;
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
274
|
+
// =========================================================================
|
|
275
|
+
// OS DETECTION
|
|
276
|
+
// =========================================================================
|
|
277
|
+
for (const { name, pattern, versionSep, versionMap } of OS_PATTERNS) {
|
|
278
|
+
const match = ua.match(pattern);
|
|
279
|
+
if (match) {
|
|
280
|
+
result.os.name = name;
|
|
281
|
+
let version = match[1] || null;
|
|
231
282
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
283
|
+
// Replace version separator (e.g., "_" with "." for iOS/macOS)
|
|
284
|
+
if (version && versionSep) {
|
|
285
|
+
version = version.replace(new RegExp(versionSep, "g"), ".");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Map version numbers (e.g., "6.1" to "7" for Windows)
|
|
289
|
+
if (version && versionMap && versionMap[version]) {
|
|
290
|
+
version = versionMap[version];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
result.os.version = version;
|
|
294
|
+
break;
|
|
238
295
|
}
|
|
296
|
+
}
|
|
239
297
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
298
|
+
// =========================================================================
|
|
299
|
+
// DEVICE TYPE DETECTION
|
|
300
|
+
// =========================================================================
|
|
301
|
+
for (const { type, pattern } of DEVICE_PATTERNS) {
|
|
302
|
+
if (pattern.test(ua)) {
|
|
303
|
+
result.device.type = type;
|
|
304
|
+
break;
|
|
246
305
|
}
|
|
306
|
+
}
|
|
247
307
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
308
|
+
// =========================================================================
|
|
309
|
+
// DEVICE VENDOR DETECTION
|
|
310
|
+
// =========================================================================
|
|
311
|
+
for (const { vendor, pattern } of DEVICE_VENDORS) {
|
|
312
|
+
if (pattern.test(ua)) {
|
|
313
|
+
result.device.vendor = vendor;
|
|
314
|
+
break;
|
|
255
315
|
}
|
|
316
|
+
}
|
|
256
317
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
318
|
+
// =========================================================================
|
|
319
|
+
// DEVICE MODEL DETECTION
|
|
320
|
+
// =========================================================================
|
|
321
|
+
for (const { pattern, extract } of MODEL_PATTERNS) {
|
|
322
|
+
const match = ua.match(pattern);
|
|
323
|
+
if (match) {
|
|
324
|
+
result.device.model = extract(match);
|
|
325
|
+
break;
|
|
263
326
|
}
|
|
327
|
+
}
|
|
264
328
|
|
|
265
|
-
|
|
266
|
-
|
|
329
|
+
// =========================================================================
|
|
330
|
+
// CPU ARCHITECTURE DETECTION
|
|
331
|
+
// =========================================================================
|
|
332
|
+
for (const { architecture, pattern } of CPU_PATTERNS) {
|
|
333
|
+
if (pattern.test(ua)) {
|
|
334
|
+
result.cpu.architecture = architecture;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
267
338
|
|
|
268
|
-
|
|
269
|
-
|
|
339
|
+
// =========================================================================
|
|
340
|
+
// BOT DETECTION
|
|
341
|
+
// =========================================================================
|
|
342
|
+
// Check all bot patterns - any match indicates a bot
|
|
343
|
+
result.isBot = BOT_PATTERNS.some((pattern) => pattern.test(ua));
|
|
270
344
|
|
|
271
|
-
|
|
272
|
-
* Create an empty result for null/undefined/empty UA
|
|
273
|
-
*/
|
|
274
|
-
function createEmptyResult(ua) {
|
|
275
|
-
return {
|
|
276
|
-
browser: { name: null, version: null, major: null },
|
|
277
|
-
engine: { name: null, version: null },
|
|
278
|
-
os: { name: null, version: null },
|
|
279
|
-
device: { type: null, vendor: null, model: null },
|
|
280
|
-
cpu: { architecture: null },
|
|
281
|
-
isBot: false,
|
|
282
|
-
raw: ua || null,
|
|
283
|
-
};
|
|
345
|
+
return result;
|
|
284
346
|
}
|
|
285
347
|
|
|
286
348
|
module.exports = parseUserAgent;
|