├── .gitignore ├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── deno.json ├── LICENSE ├── README.md ├── mod.test.ts └── mod.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules 3 | .npmrc 4 | test.js 5 | package.json 6 | package-lock.json 7 | 8 | # Deno 9 | deno.lock 10 | 11 | # Generic 12 | test.js 13 | 14 | # Bun 15 | bun.lockb 16 | bunfig.toml -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to jsr 2 | on: 3 | release: 4 | types: [released] 5 | 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | 12 | permissions: 13 | contents: read 14 | id-token: write 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Publish package 20 | run: npx jsr publish 21 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cross/runtime", 3 | "version": "1.2.1", 4 | "exports": "./mod.ts", 5 | "fmt": { 6 | "lineWidth": 200, 7 | "exclude": ["README.md"] 8 | }, 9 | "lint": { 10 | "rules": { 11 | "exclude": ["no-node-globals"] 12 | } 13 | }, 14 | "publish": { 15 | "exclude": [ 16 | ".github", 17 | "mod.test.ts" 18 | ] 19 | }, 20 | "imports": { "@cross/test": "jsr:@cross/test@~0.0.9", "@std/assert": "jsr:@std/assert@~1.0.3" } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | deno_ci: 12 | uses: cross-org/workflows/.github/workflows/deno-ci.yml@main 13 | with: 14 | entrypoint: mod.ts 15 | bun_ci: 16 | uses: cross-org/workflows/.github/workflows/bun-ci.yml@main 17 | with: 18 | jsr_dependencies: "@cross/test @std/assert" 19 | node_ci: 20 | uses: cross-org/workflows/.github/workflows/node-ci.yml@main 21 | with: 22 | jsr_dependencies: "@cross/test @std/assert" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 cross-org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## @cross/runtime 2 | 3 | [![JSR Version](https://jsr.io/badges/@cross/runtime?v=bust)](https://jsr.io/@cross/runtime) [![JSR Score](https://jsr.io/badges/@cross/runtime/score?v=bust)](https://jsr.io/@cross/runtime/score) 4 | 5 | **Cross-Runtime Environment Detection for JavaScript and TypeScript** 6 | 7 | This package provides a well defined, cross runtime, way to determine details about the current runtime environment (Deno, Bun, Node.js, Tauri, 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. 8 | 9 | Try it out at [https://jsfiddle.net/hexag0n/x9568nmy/](https://jsfiddle.net/hexag0n/x9568nmy/). 10 | 11 | Part of the @cross suite - check out our growing collection of cross-runtime tools at [github.com/cross-org](https://github.com/cross-org). 12 | 13 | ```javascript 14 | import { 15 | CurrentArchitecture, 16 | CurrentOS, 17 | CurrentProduct, 18 | CurrentRuntime, 19 | CurrentVersion, 20 | Runtime 21 | } from "@cross/runtime"; 22 | 23 | console.log(`Runtime: ${CurrentRuntime}`); 24 | console.log(`OS: ${CurrentOS}`); 25 | console.log(`Architecture: ${CurrentArchitecture}`); 26 | console.log(`Product: ${CurrentProduct}`); 27 | console.log(`Version: ${CurrentVersion}\n`); 28 | 29 | if (CurrentRuntime == Runtime.Deno) { 30 | console.log("You're running Deno!"); 31 | } else { 32 | console.log("You're not running Deno!"); 33 | } 34 | ``` 35 | 36 | This script results in something like: 37 | 38 | ``` 39 | Runtime: bun 40 | OS: linux 41 | Architecture: x86_64 42 | Product: bun 43 | Version: 1.0.30 44 | 45 | You're not running Deno! 46 | ``` 47 | 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 = getOSFromUserAgent(ua); 60 | const product = getProductFromUserAgent(ua); 61 | const version = getVersionFromUserAgent(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 | 76 | ### Documentation 77 | 78 | **Installation** 79 | 80 | ```bash 81 | # Pick your runtime and package manager: 82 | npx jsr add @cross/runtime # Node.js 83 | deno add @cross/runtime # Deno 84 | bunx jsr add @cross/runtime # Bun 85 | ``` 86 | 87 | ### Supported Environments 88 | 89 | **Runtimes** 90 | 91 | * Deno 92 | * Bun 93 | * Node.js 94 | * Tauri 95 | * Web browsers (Chrome, Firefox, Edge, Safari, Opera, Brave) 96 | * Edge Functions (Cloudflare Workers, Netlify Edge Functions, Fastly Compute@Edge) 97 | 98 | **Operating Systems** 99 | 100 | * Windows 101 | * macOS 102 | * Linux 103 | * Android 104 | * iOS 105 | * Less common Unix variants (AIX, FreeBSD, OpenBSD, etc.) 106 | 107 | **Browsers** 108 | 109 | * Chrome 110 | * Firefox 111 | * Edge 112 | * Safari 113 | * Opera 114 | * Brave 115 | 116 | **Important Notes:** 117 | 118 | * **Additional Functionality:** Beyond detection, the `dumpSystemInfo` function logs the information, and the `getsystemInfo` function provides a JSON representation. 119 | * **Tauri Detection:** Tauri applications are detected by the presence of the `__TAURI__` global object. Since Tauri runs in a webview, it uses the same OS and architecture detection logic as browsers. 120 | -------------------------------------------------------------------------------- /mod.test.ts: -------------------------------------------------------------------------------- 1 | import { CurrentOS, getOSFromUserAgent, getProductFromUserAgent, getVersionFromUserAgent, OperatingSystem } from "./mod.ts"; 2 | import { CurrentProduct, CurrentRuntime, CurrentVersion, Product, Runtime } from "./mod.ts"; 3 | import { assertEquals, assertNotEquals } from "@std/assert"; 4 | import { test } from "@cross/test"; 5 | 6 | const UserAgentStrings = { 7 | chrome128OnWindows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", 8 | chrome118OnLinux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", 9 | firefox129OnMacOS: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.6; rv:129.0) Gecko/20100101 Firefox/129.0", 10 | safari17OnIphone: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1", 11 | safari17OnMacOS: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15", 12 | safari5OnIos: "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3", 13 | }; 14 | 15 | test("Current runtime is correct (string)", () => { 16 | if ("Bun" in globalThis) { 17 | assertEquals("bun", CurrentRuntime); 18 | } else if ("Deno" in globalThis) { 19 | assertEquals("deno", CurrentRuntime); 20 | } else { 21 | assertEquals("node", CurrentRuntime); 22 | } 23 | }); 24 | test("Current runtime is Deno (enum)", () => { 25 | if ("Bun" in globalThis) { 26 | assertEquals(Runtime.Bun, CurrentRuntime); 27 | } else if ("Deno" in globalThis) { 28 | assertEquals(Runtime.Deno, CurrentRuntime); 29 | } else { 30 | assertEquals(Runtime.Node, CurrentRuntime); 31 | } 32 | }); 33 | test("Current product is Deno (string)", () => { 34 | if ("Bun" in globalThis) { 35 | assertEquals("bun", CurrentProduct); 36 | } else if ("Deno" in globalThis) { 37 | assertEquals("deno", CurrentProduct); 38 | } else { 39 | assertEquals("node", CurrentProduct); 40 | } 41 | }); 42 | test("Current product is Deno (enum)", () => { 43 | if ("Bun" in globalThis) { 44 | assertEquals(Product.Bun, CurrentProduct); 45 | } else if ("Deno" in globalThis) { 46 | assertEquals(Product.Deno, CurrentProduct); 47 | } else { 48 | assertEquals(Product.Node, CurrentProduct); 49 | } 50 | }); 51 | test("Current version contains a dot", () => { 52 | assertEquals(true, CurrentVersion?.includes(".")); 53 | }); 54 | test("Current operating system is supported", () => { 55 | assertNotEquals(OperatingSystem.Unsupported, CurrentOS); 56 | }); 57 | test("Chrome is detected", () => { 58 | assertEquals(getProductFromUserAgent(UserAgentStrings.chrome128OnWindows), Product.Chrome); 59 | }); 60 | test("Windows is detected from chrome ua", () => { 61 | assertEquals(getOSFromUserAgent(UserAgentStrings.chrome128OnWindows), OperatingSystem.Windows); 62 | }); 63 | test("Chrome version is detected from chrome ua", () => { 64 | assertEquals(getVersionFromUserAgent(UserAgentStrings.chrome128OnWindows), "128"); 65 | }); 66 | test("Chrome is detected", () => { 67 | assertEquals(getProductFromUserAgent(UserAgentStrings.chrome128OnWindows), Product.Chrome); 68 | }); 69 | test("Windows is detected from chrome ua", () => { 70 | assertEquals(getOSFromUserAgent(UserAgentStrings.chrome128OnWindows), OperatingSystem.Windows); 71 | }); 72 | test("Linux is detected from chrome ua", () => { 73 | assertEquals(getOSFromUserAgent(UserAgentStrings.chrome118OnLinux), OperatingSystem.Linux); 74 | }); 75 | test("Chrome version is detected from windows chrome ua", () => { 76 | assertEquals(getVersionFromUserAgent(UserAgentStrings.chrome128OnWindows), "128"); 77 | }); 78 | test("Chrome version is detected from linux chrome ua", () => { 79 | assertEquals(getVersionFromUserAgent(UserAgentStrings.chrome118OnLinux), "118"); 80 | }); 81 | test("Firefox is detected", () => { 82 | assertEquals(getProductFromUserAgent(UserAgentStrings.firefox129OnMacOS), Product.Firefox); 83 | }); 84 | test("MacOS is detected from Firefox user agent", () => { 85 | assertEquals(getOSFromUserAgent(UserAgentStrings.firefox129OnMacOS), OperatingSystem.macOS); 86 | }); 87 | test("Version is detected from Firefox user agent", () => { 88 | assertEquals(getVersionFromUserAgent(UserAgentStrings.firefox129OnMacOS), "129"); 89 | }); 90 | test("Safari is detected", () => { 91 | assertEquals(getProductFromUserAgent(UserAgentStrings.safari17OnIphone), Product.Safari); 92 | }); 93 | test("Ios is detected from Safari user agent", () => { 94 | assertEquals(getOSFromUserAgent(UserAgentStrings.safari17OnIphone), OperatingSystem.iOS); 95 | }); 96 | test("Version is detected from Safari user agent", () => { 97 | assertEquals(getVersionFromUserAgent(UserAgentStrings.safari17OnIphone), "17.5"); 98 | }); 99 | test("MacOS is detected from Safari user agent", () => { 100 | assertEquals(getOSFromUserAgent(UserAgentStrings.safari17OnMacOS), OperatingSystem.macOS); 101 | }); 102 | test("Version is detected from Safari user agent", () => { 103 | assertEquals(getVersionFromUserAgent(UserAgentStrings.safari17OnMacOS), "17.5"); 104 | }); 105 | test("MacOS is detected from older Safari user agent", () => { 106 | assertEquals(getOSFromUserAgent(UserAgentStrings.safari5OnIos), OperatingSystem.iOS); 107 | }); 108 | test("Version is detected from Safari user agent", () => { 109 | assertEquals(getVersionFromUserAgent(UserAgentStrings.safari5OnIos), "5.1"); 110 | }); 111 | test("Tauri detection works correctly", () => { 112 | // For now we're just checking that we're not in Tauri (we're not running in a Tauri app) 113 | assertNotEquals(CurrentRuntime, Runtime.Tauri); 114 | }); 115 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-process-global 2 | /* Private functions */ 3 | function getChromeVersion(userAgent: string) { 4 | const match = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); 5 | return match ? match[2] : "Unknown"; 6 | } 7 | 8 | function getFirefoxVersion(userAgent: string) { 9 | const match = userAgent.match(/Firefox\/([0-9]+)\./); 10 | return match ? match[1] : "Unknown"; 11 | } 12 | 13 | function getEdgeVersion(userAgent: string) { 14 | const match = userAgent.match(/Edg\/([0-9]+)\./); 15 | return match ? match[1] : "Unknown"; 16 | } 17 | 18 | function getSafariVersion(userAgent: string) { 19 | const match = userAgent.match(/Version\/([0-9]+)\.([0-9]+)(\.[0-9]+)?/); 20 | if (match) return `${match[1]}.${match[2]}`; 21 | return "Unknown"; 22 | } 23 | 24 | function getOperaVersion(userAgent: string) { 25 | // Look for either 'Opera/' or 'OPR/' followed by the version 26 | const match = userAgent.match(/(Opera|OPR)\/([0-9]+)\./); 27 | return match ? match[2] : "Unknown"; 28 | } 29 | 30 | function getBraveVersion(userAgent: string) { 31 | const match = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); 32 | return match ? match[2] : "Unknown"; 33 | } 34 | 35 | /** 36 | * Enum of supported Runtimes. 37 | * @enum {string} 38 | */ 39 | export enum Runtime { 40 | Deno = "deno", 41 | Bun = "bun", 42 | Node = "node", 43 | Browser = "browser", 44 | Tauri = "tauri", 45 | Workerd = "workerd", 46 | Netlify = "netlify", 47 | EdgeLight = "edgelight", 48 | Fastly = "fastly", 49 | Unsupported = "unsupported", 50 | } 51 | 52 | /** 53 | * Enum of supported Operating Systems. 54 | * @enum {string} 55 | */ 56 | export enum OperatingSystem { 57 | Windows = "windows", 58 | macOS = "macos", 59 | Linux = "linux", 60 | Android = "android", 61 | Unix = "unix", 62 | iOS = "ios", 63 | Unsupported = "unsupported", 64 | } 65 | 66 | /** 67 | * Enum of supported Products. 68 | * @enum {string} 69 | */ 70 | export enum Product { 71 | // All runtimes 72 | Deno = "deno", 73 | Bun = "bun", 74 | Node = "node", 75 | Tauri = "tauri", 76 | Workerd = "workerd", 77 | Netlify = "netlify", 78 | EdgeLight = "edgelight", 79 | Fastly = "fastly", 80 | 81 | // All browsers 82 | Firefox = "firefox", 83 | Safari = "safari", 84 | Chrome = "chrome", 85 | Edge = "edge", 86 | Opera = "opera", 87 | Brave = "brave", 88 | 89 | // And unsupported 90 | Unsupported = "unsupported", 91 | } 92 | 93 | /** 94 | * Enum of common CPU architectures. 95 | * @enum {string} 96 | */ 97 | export enum Architecture { 98 | x86 = "x86", 99 | x64 = "x86_64", 100 | arm = "arm", 101 | arm64 = "arm64", 102 | Unsupported = "unsupported", 103 | } 104 | 105 | ///** 106 | // * Interface for Tauri-specific information. 107 | // */ 108 | //export interface TauriInfo { 109 | // /** The application version, as defined in `tauri.conf.json`. */ 110 | // version?: string; 111 | // /** The application name, as defined in `tauri.conf.json`. */ 112 | // name?: string; 113 | // /** The application's bundle identifier, as defined in `tauri.conf.json`. */ 114 | // identifier?: string; 115 | // /** The version of the underlying Tauri runtime. */ 116 | // tauriVersion?: string; 117 | //} 118 | 119 | /** 120 | * Verifies if a property exists in the global namespace and optionally checks its type. 121 | * 122 | * @param {string} name - The name of the property to verify. 123 | * @param {string} [typeString] - The expected type of the property (optional). 124 | * @returns {boolean} True if the property exists and matches the type (if provided), False otherwise. 125 | */ 126 | function verifyGlobal(name: string, typeString?: string) { 127 | return name in globalThis && (!typeString || typeof (globalThis as Record)[name] === typeString); 128 | } 129 | 130 | // Disable getTauriInfo() while I figure some things out. 131 | ///** 132 | // * Gets Tauri-specific information asynchronously. 133 | // * Only works when running in a Tauri application. 134 | // * @returns {Promise} Tauri-specific data 135 | // */ 136 | //export async function getTauriInfo(): Promise { 137 | // if (getCurrentRuntime() !== Runtime.Tauri) { 138 | // return {}; 139 | // } 140 | // 141 | // try { 142 | // const { getVersion, getName, getIdentifier, getTauriVersion } = await import("@tauri-apps/api/app"); 143 | // 144 | // const [version, name, identifier, tauriVersion] = await Promise.all([ 145 | // getVersion().catch(() => undefined), 146 | // getName().catch(() => undefined), 147 | // getIdentifier().catch(() => undefined), 148 | // getTauriVersion().catch(() => undefined), 149 | // ]); 150 | // 151 | // return { version, name, identifier, tauriVersion }; 152 | // } catch (_e) { 153 | // return {}; 154 | // } 155 | //} 156 | 157 | /** 158 | * Dynamically determines the current runtime environment. 159 | */ 160 | export function getCurrentRuntime(): Runtime { 161 | if (verifyGlobal("Deno", "object")) return Runtime.Deno; 162 | if (verifyGlobal("Bun", "object")) return Runtime.Bun; 163 | if (verifyGlobal("Netlify", "object")) return Runtime.Netlify; 164 | if (verifyGlobal("EdgeRuntime", "string")) return Runtime.EdgeLight; 165 | if (globalThis.navigator?.userAgent === "Cloudflare-Workers") return Runtime.Workerd; 166 | if (verifyGlobal("fastly", "object")) return Runtime.Fastly; 167 | if ( 168 | verifyGlobal("process", "object") && 169 | //@ts-ignore Runtime detection 170 | typeof process.versions !== "undefined" && 171 | //@ts-ignore Runtime detection 172 | typeof process.versions.node !== "undefined" 173 | ) { 174 | return Runtime.Node; 175 | } 176 | if (verifyGlobal("window", "object")) { // Check for Browser or Tauri 177 | // Check for Tauri first (Tauri runs in a webview, so it has window) 178 | if (verifyGlobal("__TAURI__", "object")) return Runtime.Tauri; 179 | return Runtime.Browser; 180 | } 181 | return Runtime.Unsupported; 182 | } 183 | 184 | /** 185 | * Dynamically determines the current operating system. 186 | */ 187 | export function getCurrentOS(): OperatingSystem { 188 | const runtime = getCurrentRuntime(); 189 | switch (runtime) { 190 | case Runtime.Deno: 191 | switch (Deno.build.os) { 192 | case "darwin": 193 | return OperatingSystem.macOS; 194 | case "windows": 195 | return OperatingSystem.Windows; 196 | case "linux": 197 | return OperatingSystem.Linux; 198 | case "android": 199 | return OperatingSystem.Android; 200 | case "aix": 201 | case "freebsd": 202 | case "illumos": 203 | case "netbsd": 204 | case "solaris": 205 | return OperatingSystem.Unix; 206 | } 207 | return OperatingSystem.Unsupported; 208 | case Runtime.Node: 209 | // @ts-ignore Cross Runtime 210 | switch (process.platform) { 211 | case "darwin": 212 | return OperatingSystem.macOS; 213 | case "win32": 214 | return OperatingSystem.Windows; 215 | case "linux": 216 | return OperatingSystem.Linux; 217 | case "android": 218 | return OperatingSystem.Android; 219 | case "aix": 220 | case "freebsd": 221 | case "openbsd": 222 | case "sunos": 223 | return OperatingSystem.Unix; 224 | } 225 | return OperatingSystem.Unsupported; 226 | case Runtime.Bun: 227 | // @ts-ignore Cross Runtime 228 | switch (process.platform) { 229 | case "darwin": 230 | return OperatingSystem.macOS; 231 | case "win32": 232 | return OperatingSystem.Windows; 233 | case "linux": 234 | return OperatingSystem.Linux; 235 | case "android": 236 | return OperatingSystem.Android; 237 | case "aix": 238 | case "freebsd": 239 | case "openbsd": 240 | case "sunos": 241 | return OperatingSystem.Unix; 242 | } 243 | return OperatingSystem.Unsupported; 244 | case Runtime.Tauri: 245 | case Runtime.Browser: { 246 | if ("userAgent" in navigator) { 247 | const userAgent = navigator.userAgent; 248 | return getOSFromUserAgent(userAgent); 249 | } 250 | } 251 | } 252 | return OperatingSystem.Unsupported; 253 | } 254 | 255 | /** 256 | * Determine operating system from user agent string, if possible 257 | */ 258 | export function getOSFromUserAgent(userAgent: string): OperatingSystem { 259 | if (userAgent.indexOf("Win") !== -1) return OperatingSystem.Windows; 260 | if (userAgent.indexOf("like Mac") !== -1) return OperatingSystem.iOS; 261 | if (userAgent.indexOf("Mac") !== -1) return OperatingSystem.macOS; 262 | if (userAgent.indexOf("Android") !== -1) return OperatingSystem.Android; 263 | if (userAgent.indexOf("X11") !== -1 || userAgent.indexOf("Linux") !== -1) return OperatingSystem.Linux; 264 | return OperatingSystem.Unsupported; 265 | } 266 | 267 | /** 268 | * Dynamically determines the current browser and its version (if applicable). 269 | */ 270 | export function getCurrentProduct(): Product { 271 | const runtime = getCurrentRuntime(); 272 | switch (runtime) { 273 | case Runtime.Deno: 274 | return Product.Deno; 275 | case Runtime.Node: 276 | return Product.Node; 277 | case Runtime.Bun: 278 | return Product.Bun; 279 | case Runtime.Tauri: 280 | return Product.Tauri; 281 | case Runtime.Workerd: 282 | return Product.Workerd; 283 | case Runtime.Netlify: 284 | return Product.Netlify; 285 | case Runtime.EdgeLight: 286 | return Product.EdgeLight; 287 | case Runtime.Fastly: 288 | return Product.Fastly; 289 | case Runtime.Browser: { 290 | // Brave can not be detected from user agent string, handle separately 291 | if (verifyGlobal("brave") && "brave" in navigator) return Product.Brave; 292 | // For browser, get the specific browser 293 | const userAgent = navigator.userAgent; 294 | return getProductFromUserAgent(userAgent); 295 | } 296 | default: 297 | return Product.Unsupported; 298 | } 299 | } 300 | 301 | /** 302 | * Determines the product from a user agent string, if possible 303 | */ 304 | export function getProductFromUserAgent(userAgent: string): Product { 305 | if (userAgent.indexOf("Opera") !== -1 || userAgent.indexOf("OPR") !== -1) return Product.Opera; 306 | if (userAgent.indexOf("Safari") !== -1 && userAgent.indexOf("Chrome") === -1) return Product.Safari; 307 | if (userAgent.indexOf("Edg") !== -1) return Product.Edge; 308 | if (userAgent.indexOf("Chrome") !== -1) return Product.Chrome; 309 | if (userAgent.indexOf("Firefox") !== -1) return Product.Firefox; 310 | return Product.Unsupported; 311 | } 312 | 313 | /** 314 | * Dynamically determines the version of the current product/runtime 315 | * @returns {string} A string containing the detected version, or undefined if the product is not supported. 316 | */ 317 | export function getCurrentVersion(): string | undefined { 318 | const product = getCurrentProduct(); 319 | switch (product) { 320 | case Product.Deno: 321 | // @ts-ignore Runtime detection 322 | return Deno.version.deno; 323 | case Product.Node: 324 | // @ts-ignore Runtime detection 325 | return process.versions.node; 326 | case Product.Bun: 327 | // @ts-ignore Runtime detection 328 | return process.versions.bun; 329 | case Product.Tauri: //Fallthrough to default handling. 330 | default: { 331 | const userAgent = globalThis.navigator?.userAgent; 332 | return getVersionFromUserAgent(userAgent); 333 | } 334 | } 335 | } 336 | 337 | /** 338 | * Determines the product version from a user agent string, if possible 339 | */ 340 | export function getVersionFromUserAgent(userAgent: string): string | undefined { 341 | const product = getProductFromUserAgent(userAgent); 342 | switch (product) { 343 | case Product.Chrome: 344 | return getChromeVersion(userAgent); 345 | case Product.Firefox: 346 | return getFirefoxVersion(userAgent); 347 | case Product.Edge: 348 | return getEdgeVersion(userAgent); 349 | case Product.Safari: 350 | return getSafariVersion(userAgent); 351 | case Product.Opera: 352 | return getOperaVersion(userAgent); 353 | case Product.Brave: 354 | return getBraveVersion(userAgent); 355 | default: 356 | return undefined; 357 | } 358 | } 359 | 360 | /** 361 | * Attempts to determine the current CPU architecture of the runtime's environment. 362 | */ 363 | export function getCurrentArchitecture(): Architecture { 364 | const runtime = getCurrentRuntime(); 365 | switch (runtime) { 366 | case Runtime.Deno: 367 | if (Deno.build.arch === "x86_64") return Architecture.x64; 368 | if (Deno.build.arch === "aarch64") return Architecture.arm64; 369 | if (Deno.build.os === "darwin") return Architecture.x64; 370 | return Architecture.x86; 371 | case Runtime.Bun: 372 | case Runtime.Node: 373 | // @ts-ignore Cross Runtime 374 | switch (process.arch) { 375 | case "arm": 376 | return Architecture.arm; 377 | case "arm64": 378 | return Architecture.arm64; 379 | case "ia32": 380 | return Architecture.x86; 381 | case "x64": 382 | return Architecture.x64; 383 | case "loong64": 384 | case "mips": 385 | case "mipsel": 386 | case "ppc": 387 | case "ppc64": 388 | case "riscv64": 389 | case "s390": 390 | case "s390x": 391 | return Architecture.Unsupported; 392 | } 393 | return Architecture.Unsupported; 394 | case Runtime.Tauri: 395 | case Runtime.Browser: { 396 | const userAgent = navigator.userAgent; 397 | // @ts-ignore Cross Runtime 398 | const platform = navigator.platform; 399 | 400 | if (platform.indexOf("Win64") !== -1 || platform.indexOf("x64") !== -1 || platform.indexOf("x86_64") !== -1) return Architecture.x64; 401 | if (platform.indexOf("Win32") !== -1 || (platform.indexOf("x86") !== -1 && platform.indexOf("x86_64") === -1)) return Architecture.x86; 402 | if (userAgent.indexOf("Win64") !== -1 || userAgent.indexOf("x64") !== -1 || userAgent.indexOf("x86_64") !== -1) return Architecture.x64; 403 | if (userAgent.indexOf("Win32") !== -1 || (userAgent.indexOf("x86") !== -1 && userAgent.indexOf("x86_64") === -1)) return Architecture.x86; 404 | 405 | if (userAgent.indexOf("arm64") !== -1) return Architecture.arm64; 406 | if (userAgent.indexOf("arm") !== -1) { 407 | return Architecture.arm; 408 | } 409 | // @ts-ignore Cross Runtime 410 | if (platform.indexOf("iPhone") || platform.indexOf("iPad") || (userAgent.indexOf("Mac") !== -1 && "ontouchend" in document)) { 411 | // Likely aarch64 on newer iOS devices and Apple Silicon Macs 412 | return Architecture.arm64; 413 | } 414 | return Architecture.Unsupported; 415 | } 416 | } 417 | return Architecture.Unsupported; 418 | } 419 | 420 | /** 421 | * Represents a group of system information gathered by the library. 422 | */ 423 | interface SystemInfo { 424 | runtime: Runtime; 425 | product: Product; 426 | version: string | undefined; 427 | os: OperatingSystem | undefined; 428 | architecture: Architecture | undefined; 429 | } 430 | 431 | /** 432 | * Retrieves the current system information. 433 | * @returns {SystemInfo} An object containing the system information. 434 | */ 435 | function getSystemInfoInternal(): SystemInfo { 436 | const systemInfo: SystemInfo = { 437 | runtime: CurrentRuntime, 438 | product: CurrentProduct, 439 | version: CurrentVersion, 440 | os: CurrentOS, 441 | architecture: CurrentArchitecture, 442 | }; 443 | return systemInfo; 444 | } 445 | 446 | /** 447 | * Logs current system information to the console. 448 | * 449 | * @param {boolean} [useTable=false] - If true, formats the output as a table. 450 | */ 451 | export function dumpSystemInfo(useTable = false): void { 452 | const systemInfo: SystemInfo = getSystemInfoInternal(); 453 | 454 | if (useTable) { 455 | console.table(systemInfo); 456 | } else { 457 | console.log(JSON.stringify(systemInfo, null, 2)); 458 | } 459 | } 460 | 461 | /** 462 | * Gets the current system information as a formatted JSON string. 463 | * @returns {string} 464 | */ 465 | export function getSystemInfo(): string { 466 | const systemInfo: SystemInfo = getSystemInfoInternal(); 467 | return JSON.stringify(systemInfo); 468 | } 469 | 470 | /** 471 | * Static variable containing the current runtime. 472 | */ 473 | export const CurrentRuntime: Runtime = getCurrentRuntime(); 474 | 475 | /** 476 | * Static variable containing the current product. 477 | */ 478 | export const CurrentProduct: Product = getCurrentProduct(); 479 | 480 | /** 481 | * Static variable containing the current product/runtime version. 482 | */ 483 | export const CurrentVersion: string | undefined = getCurrentVersion(); 484 | 485 | /** 486 | * Static variable containing the current operating system. 487 | */ 488 | export const CurrentOS: OperatingSystem | undefined = getCurrentOS(); 489 | 490 | /** 491 | * Static variable containing the current operating system. 492 | */ 493 | export const CurrentArchitecture: Architecture | undefined = getCurrentArchitecture(); 494 | --------------------------------------------------------------------------------