From a6c41840542011c938af32585b8fdc2e12ac3828 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 26 Mar 2026 15:48:05 +0100 Subject: [PATCH] Replace ua-parser-js with simple regexs (#30355) --- package.json | 2 -- src/resources/log-message.ts | 47 ++++++++++++++++++++++++++++-------- yarn.lock | 43 --------------------------------- 3 files changed, 37 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index f4e790586a..f0c59dea68 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,6 @@ "stacktrace-js": "2.0.2", "superstruct": "2.0.2", "tinykeys": "3.0.0", - "ua-parser-js": "2.0.9", "weekstart": "2.0.0", "workbox-cacheable-response": "7.4.0", "workbox-core": "7.4.0", @@ -169,7 +168,6 @@ "@types/qrcode": "1.5.6", "@types/sortablejs": "1.15.9", "@types/tar": "7.0.87", - "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", "@vitest/coverage-v8": "4.1.0", "babel-loader": "10.1.1", diff --git a/src/resources/log-message.ts b/src/resources/log-message.ts index 28f810b897..a2a81370c8 100644 --- a/src/resources/log-message.ts +++ b/src/resources/log-message.ts @@ -1,11 +1,44 @@ import { fromError } from "stacktrace-js"; -import { UAParser } from "ua-parser-js"; // URL paths to remove from filenames and max stack trace lines for brevity const REMOVAL_PATHS = /^\/(?:home-assistant\/frontend\/[^/]+|unknown|\/{2}\.)\//; const MAX_STACK_FRAMES = 10; +// Order matters: more specific UA tokens must come before generic ones +const BROWSER_PATTERNS: [RegExp, string][] = [ + [/Edg\/(\S+)/, "Edge"], + [/OPR\/(\S+)/, "Opera"], + [/Chrome\/(\S+)/, "Chrome"], + [/Firefox\/(\S+)/, "Firefox"], + [/Version\/(\S+).*Safari/, "Safari"], +]; + +const OS_PATTERNS: [RegExp, string][] = [ + [/CrOS/, "Chrome OS"], + [/Windows NT ([\d.]+)/, "Windows"], + [/Android ([\d.]+)/, "Android"], + [/iPhone OS ([\d_.]+)/, "iOS"], + [/iPad; CPU OS ([\d_.]+)/, "iPadOS"], + [/Mac OS X ([\d_.]+)/, "macOS"], + [/Linux/, "Linux"], +]; + +const detectFromUA = ( + ua: string, + patterns: [RegExp, string][], + fallback: string +): string => { + for (const [pattern, name] of patterns) { + const match = ua.match(pattern); + if (match) { + const version = match[1]?.replace(/_/g, "."); + return version ? `${name} ${version}` : name; + } + } + return fallback; +}; + export const createLogMessage = async ( error: unknown, intro?: string, @@ -15,15 +48,9 @@ export const createLogMessage = async ( const lines: (string | undefined)[] = []; // Append the originating browser/OS to any intro for easier identification if (intro) { - const parser = new UAParser(); - const { - name: browserName = "unknown browser", - version: browserVersion = "", - } = parser.getBrowser(); - const { name: osName = "unknown OS", version: osVersion = "" } = - parser.getOS(); - const browser = `${browserName} ${browserVersion}`.trim(); - const os = `${osName} ${osVersion}`.trim(); + const ua = navigator.userAgent; + const browser = detectFromUA(ua, BROWSER_PATTERNS, "unknown browser"); + const os = detectFromUA(ua, OS_PATTERNS, "unknown OS"); lines.push(`${intro} from ${browser} on ${os}`); } // In most cases, an Error instance will be thrown, which can have many details to log: diff --git a/yarn.lock b/yarn.lock index 5a751b0b72..b0238f8fd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4862,13 +4862,6 @@ __metadata: languageName: node linkType: hard -"@types/ua-parser-js@npm:0.7.39": - version: 0.7.39 - resolution: "@types/ua-parser-js@npm:0.7.39" - checksum: 10/8d173a79b37f9404cda49e848d82694c4854828ff20d94ddfba53bf9ccd9b4df16bf28f72786cf253a73305e9a54d2f2bb54ebfc144fca2496d3f3f025a79cb5 - languageName: node - linkType: hard - "@types/webspeechapi@npm:0.0.29": version: 0.0.29 resolution: "@types/webspeechapi@npm:0.0.29" @@ -7006,13 +6999,6 @@ __metadata: languageName: node linkType: hard -"detect-europe-js@npm:^0.1.2": - version: 0.1.2 - resolution: "detect-europe-js@npm:0.1.2" - checksum: 10/3d6dcf6ac451baa3050ff00e05cf1841e208bc4f27c69ec2c8df5fdf2375403cf2911f8059c9c18f258d7e176c8d308e4e0e8552500d0e30a9e95a514366ef13 - languageName: node - linkType: hard - "detect-file@npm:^1.0.0": version: 1.0.0 resolution: "detect-file@npm:1.0.0" @@ -8941,7 +8927,6 @@ __metadata: "@types/qrcode": "npm:1.5.6" "@types/sortablejs": "npm:1.15.9" "@types/tar": "npm:7.0.87" - "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" "@vibrant/color": "npm:4.0.4" "@vitest/coverage-v8": "npm:4.1.0" @@ -9026,7 +9011,6 @@ __metadata: ts-lit-plugin: "npm:2.0.2" typescript: "npm:5.9.3" typescript-eslint: "npm:8.57.1" - ua-parser-js: "npm:2.0.9" vite-tsconfig-paths: "npm:6.1.1" vitest: "npm:4.1.0" webpack-stats-plugin: "npm:1.1.3" @@ -9779,13 +9763,6 @@ __metadata: languageName: node linkType: hard -"is-standalone-pwa@npm:^0.1.1": - version: 0.1.1 - resolution: "is-standalone-pwa@npm:0.1.1" - checksum: 10/bbd2ee7cbea985139f66fe8785e7699f52311e9c14d74190659885222b79dd1e8845b02f69b9221a23a2b4b00e8d4bea0a5a2603b2f26cb6d2071d46093ccf84 - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -13964,26 +13941,6 @@ __metadata: languageName: node linkType: hard -"ua-is-frozen@npm:^0.1.2": - version: 0.1.2 - resolution: "ua-is-frozen@npm:0.1.2" - checksum: 10/0113bed77d437a3752323175f01057dd01911506225e9d33799799188a89a230ab63d2445b096d4399ea8eb94e3163608f7ac2425fb8edd0844d891490607e14 - languageName: node - linkType: hard - -"ua-parser-js@npm:2.0.9": - version: 2.0.9 - resolution: "ua-parser-js@npm:2.0.9" - dependencies: - detect-europe-js: "npm:^0.1.2" - is-standalone-pwa: "npm:^0.1.1" - ua-is-frozen: "npm:^0.1.2" - bin: - ua-parser-js: script/cli.js - checksum: 10/63e3404e16ade8a7d1b45bf2a871410193ab63620ab02ffa5308a507a984d2698bd74651ce260ac3e0cb9f8df89c71fa55a6beefbe269c12849c6d6cf0974407 - languageName: node - linkType: hard - "ua-regexes-lite@npm:^1.2.1": version: 1.2.1 resolution: "ua-regexes-lite@npm:1.2.1"