Skip to content

Commit 7972959

Browse files
committed
Add User Agent String parser.
1 parent 72d8b05 commit 7972959

File tree

3 files changed

+89
-40
lines changed

3 files changed

+89
-40
lines changed

README.md

+30-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**Cross-Runtime Environment Detection for JavaScript and TypeScript**
66

7-
This package provides a well defined, cross runtime, way to determine details about the current runtime environment (Deno, Bun, Node.js, or browser) along with detailed browser detection.
7+
This package provides a well defined, cross runtime, way to determine details about the current runtime environment (Deno, Bun, Node.js, or browser) along with detailed browser detection. Since version `1.1.0`, it can also parse a User Agent string to extract OS, Product and Version in a reliable way.
88

99
Try it out at [https://jsfiddle.net/hexag0n/x9568nmy/](https://jsfiddle.net/hexag0n/x9568nmy/).
1010

@@ -17,7 +17,7 @@ import {
1717
CurrentProduct,
1818
CurrentRuntime,
1919
CurrentVersion,
20-
Runtime
20+
Runtime
2121
} from "@cross/runtime";
2222

2323
console.log(`Runtime: ${CurrentRuntime}`);
@@ -45,6 +45,34 @@ Version: 1.0.30
4545
You're not running Deno!
4646
```
4747

48+
... and an example of parsing User Agent String:
49+
50+
```javascript
51+
import {
52+
getVersionFromUserAgent,
53+
getProductFromUserAgent,
54+
getOSFromUserAgent
55+
} from "@cross/runtime";
56+
57+
const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36";
58+
59+
const os = getVersionFromUserAgent(ua);
60+
const product = getProductFromUserAgent(ua);
61+
const version = getOSFromUserAgent(ua);
62+
63+
console.log(`OS: ${os}`);
64+
console.log(`Product: ${product}`);
65+
console.log(`Version: ${version}\n`);
66+
```
67+
68+
Resulting in:
69+
70+
```
71+
OS: windows
72+
Product: chrome
73+
Version: 128
74+
```
75+
4876
### Documentation
4977

5078
**Installation**

deno.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cross/runtime",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"exports": "./mod.ts",
55
"fmt": {
66
"lineWidth": 200,
@@ -12,5 +12,5 @@
1212
"mod.test.ts"
1313
]
1414
},
15-
"imports": { "@cross/test": "jsr:@cross/test@^0.0.7", "@std/assert": "jsr:@std/assert@^0.219.1" }
15+
"imports": { "@cross/test": "jsr:@cross/test@~0.0.9", "@std/assert": "jsr:@std/assert@~1.0.3" }
1616
}

mod.ts

+57-36
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,33 @@
11
/* Private functions */
2-
function getChromeVersion() {
3-
const ua = navigator.userAgent;
4-
const match = ua.match(/Chrom(e|ium)\/([0-9]+)\./);
2+
function getChromeVersion(userAgent: string) {
3+
const match = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
54
return match ? match[2] : "Unknown";
65
}
76

8-
function getFirefoxVersion() {
9-
const ua = navigator.userAgent;
10-
const match = ua.match(/Firefox\/([0-9]+)\./);
7+
function getFirefoxVersion(userAgent: string) {
8+
const match = userAgent.match(/Firefox\/([0-9]+)\./);
119
return match ? match[1] : "Unknown";
1210
}
1311

14-
function getEdgeVersion() {
15-
const ua = navigator.userAgent;
16-
const match = ua.match(/Edg\/([0-9]+)\./);
12+
function getEdgeVersion(userAgent: string) {
13+
const match = userAgent.match(/Edg\/([0-9]+)\./);
1714
return match ? match[1] : "Unknown";
1815
}
1916

20-
function getSafariVersion() {
21-
const ua = navigator.userAgent;
22-
const match = ua.match(/Version\/([0-9]+)\.([0-9]+)(\.[0-9]+)? Safari\//);
17+
function getSafariVersion(userAgent: string) {
18+
const match = userAgent.match(/Version\/([0-9]+)\.([0-9]+)(\.[0-9]+)? Safari\//);
2319
if (match) return `${match[1]}.${match[2]}`; // Could include 3rd part if present
2420
return "Unknown";
2521
}
2622

27-
function getOperaVersion() {
28-
const ua = navigator.userAgent;
23+
function getOperaVersion(userAgent: string) {
2924
// Look for either 'Opera/' or 'OPR/' followed by the version
30-
const match = ua.match(/(Opera|OPR)\/([0-9]+)\./);
25+
const match = userAgent.match(/(Opera|OPR)\/([0-9]+)\./);
3126
return match ? match[2] : "Unknown";
3227
}
3328

34-
function getBraveVersion() {
35-
const ua = navigator.userAgent;
36-
const match = ua.match(/Chrom(e|ium)\/([0-9]+)\./);
29+
function getBraveVersion(userAgent: string) {
30+
const match = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
3731
return match ? match[2] : "Unknown";
3832
}
3933

@@ -204,17 +198,25 @@ export function getCurrentOS(): OperatingSystem {
204198
case Runtime.Browser: {
205199
if ("userAgent" in navigator) {
206200
const userAgent = navigator.userAgent;
207-
if (userAgent.indexOf("Win") !== -1) return OperatingSystem.Windows;
208-
if (userAgent.indexOf("like Mac") !== -1) return OperatingSystem.iOS;
209-
if (userAgent.indexOf("Mac") !== -1) return OperatingSystem.macOS;
210-
if (userAgent.indexOf("Android") !== -1) return OperatingSystem.Android;
211-
if (userAgent.indexOf("X11") !== -1 || userAgent.indexOf("Linux") !== -1) return OperatingSystem.Linux;
201+
return getOSFromUserAgent(userAgent);
212202
}
213203
}
214204
}
215205
return OperatingSystem.Unsupported;
216206
}
217207

208+
/**
209+
* Determine operating system from user agent string, if possible
210+
*/
211+
export function getOSFromUserAgent(userAgent: string): OperatingSystem {
212+
if (userAgent.indexOf("Win") !== -1) return OperatingSystem.Windows;
213+
if (userAgent.indexOf("like Mac") !== -1) return OperatingSystem.iOS;
214+
if (userAgent.indexOf("Mac") !== -1) return OperatingSystem.macOS;
215+
if (userAgent.indexOf("Android") !== -1) return OperatingSystem.Android;
216+
if (userAgent.indexOf("X11") !== -1 || userAgent.indexOf("Linux") !== -1) return OperatingSystem.Linux;
217+
return OperatingSystem.Unsupported;
218+
}
219+
218220
/**
219221
* Dynamically determines the current browser and its version (if applicable).
220222
*/
@@ -238,19 +240,26 @@ export function getCurrentProduct(): Product {
238240
case Runtime.Browser: {
239241
// For browser, get the specific browser
240242
const userAgent = navigator.userAgent;
241-
if (userAgent.indexOf("Opera") !== -1 || userAgent.indexOf("OPR") !== -1) return Product.Opera;
242-
if ("brave" in navigator) return Product.Brave;
243-
if (userAgent.indexOf("Safari") !== -1 && userAgent.indexOf("Chrome") === -1) return Product.Safari;
244-
if (userAgent.indexOf("Edg") !== -1) return Product.Edge;
245-
if (userAgent.indexOf("Chrome") !== -1) return Product.Chrome;
246-
if (userAgent.indexOf("Firefox") !== -1) return Product.Firefox;
247-
return Product.Unsupported;
243+
return getProductFromUserAgent(userAgent);
248244
}
249245
default:
250246
return Product.Unsupported;
251247
}
252248
}
253249

250+
/**
251+
* Determines the product from a user agent string, if possible
252+
*/
253+
export function getProductFromUserAgent(userAgent: string): Product {
254+
if (userAgent.indexOf("Opera") !== -1 || userAgent.indexOf("OPR") !== -1) return Product.Opera;
255+
if ("brave" in navigator) return Product.Brave;
256+
if (userAgent.indexOf("Safari") !== -1 && userAgent.indexOf("Chrome") === -1) return Product.Safari;
257+
if (userAgent.indexOf("Edg") !== -1) return Product.Edge;
258+
if (userAgent.indexOf("Chrome") !== -1) return Product.Chrome;
259+
if (userAgent.indexOf("Firefox") !== -1) return Product.Firefox;
260+
return Product.Unsupported;
261+
}
262+
254263
/**
255264
* Dynamically determines the version of the current product/runtime
256265
* @returns {string} A string containing the detected version, or undefined if the product is not supported.
@@ -267,18 +276,30 @@ export function getCurrentVersion(): string | undefined {
267276
case Product.Bun:
268277
// @ts-ignore Runtime detection
269278
return process.versions.bun;
279+
default:
280+
const userAgent = globalThis.navigator?.userAgent;
281+
return getVersionFromUserAgent(userAgent);
282+
}
283+
}
284+
285+
/**
286+
* Determines the product version from a user agent string, if possible
287+
*/
288+
export function getVersionFromUserAgent(userAgent: string): string | undefined {
289+
const product = getProductFromUserAgent(userAgent);
290+
switch (product) {
270291
case Product.Chrome:
271-
return getChromeVersion();
292+
return getChromeVersion(userAgent);
272293
case Product.Firefox:
273-
return getFirefoxVersion();
294+
return getFirefoxVersion(userAgent);
274295
case Product.Edge:
275-
return getEdgeVersion();
296+
return getEdgeVersion(userAgent);
276297
case Product.Safari:
277-
return getSafariVersion();
298+
return getSafariVersion(userAgent);
278299
case Product.Opera:
279-
return getOperaVersion();
300+
return getOperaVersion(userAgent);
280301
case Product.Brave:
281-
return getBraveVersion();
302+
return getBraveVersion(userAgent);
282303
default:
283304
return undefined;
284305
}

0 commit comments

Comments
 (0)