From b9d0e4cebdddb351ad1a862616af3c9cc531e420 Mon Sep 17 00:00:00 2001 From: David Evans Date: Sat, 30 Oct 2021 15:46:19 +0100 Subject: [PATCH] Replace ua-parser-js with a reduced in-house version --- lib/helper.js | 127 ++++++++++++++++++++++++++++++++++++++++++++-- package-lock.json | 6 --- package.json | 1 - 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/lib/helper.js b/lib/helper.js index e77418039..1f0392ff5 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -3,15 +3,132 @@ const fs = require('graceful-fs') const path = require('path') const _ = require('lodash') -const useragent = require('ua-parser-js') const mm = require('minimatch') +const extractUAParts = (ua) => { + const result = {} + if (ua) { + // general format is: Thing1/Version.1 Thing 2/Version.2 (detailA; detailB) + const partPattern = /(\w[\w_ ]*)\/([^ ]+)(?: \(([^)]+)\) *| (?!\()|$)/g + + for (const [, name, version, bracketed] of ua.matchAll(partPattern)) { + const details = (bracketed || '').split(/; */g).filter((x) => x) + const key = name.toLowerCase().replace(/ /g, '_') + if (!result[key]) { // use first occurrence, and prevent __proto__ changes, etc. + result[key] = { + version, + details, + hasDetail: (test) => details.some((v) => test.exec(v)), + getDetail: (test, mapper) => { + const found = details.map((v) => test.exec(v)).filter((x) => x)[0] + if (mapper) { + return found ? mapper(found) : null + } + return found || [] + } + } + // lots of checks look at the details of the first entry, which is + // usually 'Mozilla', but not always (e.g. 'Opera'), + // so record the first entry separately: + if (!result.firstEntry) { + result.firstEntry = result[key] + } + } + } + } + if (!result.firstEntry) { + // ensure firstEntry is always set no matter what (null object pattern) + result.firstEntry = { + version: null, + details: [], + hasDetail: () => false, + getDetail: () => [] + } + } + return result +} + +const extractFromParts = (parts, checks) => checks + .map((check) => check(parts)) + .filter((result) => result)[0] || [] + +const extractMacVersion = (m) => m[1].replace(/_/g, '.') + +const WINDOWS_NT_VERSION_MAP = { + 5.1: 'XP', + 5.2: 'XP', + '6.0': 'Vista', + 6.1: '7', + 6.2: '8', + 6.3: '8.1', + 6.4: '10', + '10.0': '10' +} +const extractWindowsVersion = (m) => WINDOWS_NT_VERSION_MAP[m[1]] + +const UA_BROWSERS = [ + ({ phantomjs }) => phantomjs && // also contains Safari + ['PhantomJS', phantomjs.version], + + ({ headlesschrome }) => headlesschrome && // also contains Safari + ['Chrome Headless', headlesschrome.version], + + ({ opera, version }) => opera && + ['Opera', version && version.version], + + ({ firefox }) => firefox && + ['Firefox', firefox.version], + + ({ edg }) => edg && // also contains Chrome, Safari + ['Edge', edg.version], + + ({ chrome }) => chrome && // also contains Safari + ['Chrome', chrome.version], + + ({ firstEntry, version }) => firstEntry.hasDetail(/^iphone/i) && // also contains Safari + ['Mobile Safari', version && version.version], + + ({ firstEntry, version }) => firstEntry.hasDetail(/^android/i) && // also contains Safari + ['Android Browser', version && version.version], + + ({ safari, version }) => safari && + ['Safari', version && version.version], + + ({ firstEntry }) => firstEntry.hasDetail(/^msie/i) && + ['IE', firstEntry.getDetail(/^msie ([\d.]+)/i)[1]] +] + +const UA_SYSTEMS = [ + ({ firstEntry }) => firstEntry.hasDetail(/^android/i) && + ['Android', firstEntry.getDetail(/^android ([\d.]+)/i)[1]], + + ({ firstEntry }) => firstEntry.hasDetail(/^iphone/i) && + ['iOS', firstEntry.getDetail(/iphone os ([\d._]+)/i, extractMacVersion)], + + ({ ubuntu }) => ubuntu && + ['Ubuntu', ubuntu.version], + + ({ firstEntry }) => firstEntry.hasDetail(/^freebsd/i) && + ['FreeBSD', null], + + ({ firstEntry }) => firstEntry.hasDetail(/^linux/i) && + ['Linux', firstEntry.getDetail(/^linux (.+)/i)[1]], + + ({ firstEntry }) => firstEntry.hasDetail(/mac os/i) && + ['Mac OS', firstEntry.getDetail(/mac os(?: x)? ([\d._]+)/i, extractMacVersion)], + + ({ firstEntry }) => firstEntry.hasDetail(/^windows/i) && + ['Windows', firstEntry.getDetail(/windows nt ([\d.]+)/i, extractWindowsVersion)] +] + exports.browserFullNameToShort = (fullName) => { - const ua = useragent(fullName) - if (!ua.browser.name && !ua.browser.version && !ua.os.name && !ua.os.version) { - return fullName + const parts = extractUAParts(fullName) + const [browserName, browserVersion] = extractFromParts(parts, UA_BROWSERS) + const [osName, osVersion] = extractFromParts(parts, UA_SYSTEMS) + if (browserName || osName) { + return `${browserName || 'unknown'} ${browserVersion || '0.0.0'} (${osName || 'unknown'} ${osVersion || '0.0.0'})` } - return `${ua.browser.name} ${ua.browser.version || '0.0.0'} (${ua.os.name} ${ua.os.version || '0.0.0'})` + return fullName || 'unknown' } exports.isDefined = (value) => { diff --git a/package-lock.json b/package-lock.json index f46acaa45..999167cd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5439,7 +5439,6 @@ "socket.io": "^4.2.0", "source-map": "^0.6.1", "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", "yargs": "^16.1.1" } }, @@ -10927,11 +10926,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "ua-parser-js": { - "version": "0.7.30", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.30.tgz", - "integrity": "sha512-uXEtSresNUlXQ1QL4/3dQORcGv7+J2ookOG2ybA/ga9+HYEXueT2o+8dUJQkpedsyTyCJ6jCCirRcKtdtx1kbg==" - }, "uglify-js": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.2.tgz", diff --git a/package.json b/package.json index af665bfa0..d0a47b192 100644 --- a/package.json +++ b/package.json @@ -440,7 +440,6 @@ "socket.io": "^4.2.0", "source-map": "^0.6.1", "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", "yargs": "^16.1.1" }, "devDependencies": {