import browserParsersList from './parser-browsers.js'; import osParsersList from './parser-os.js'; import platformParsersList from './parser-platforms.js'; import enginesParsersList from './parser-engines.js'; import Utils from './utils.js'; /** * The main class that arranges the whole parsing process. */ class Parser { /** * Create instance of Parser * * @param {String} UA User-Agent string * @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance * improvements if you need to make a more particular parsing * like {@link Parser#parseBrowser} or {@link Parser#parsePlatform} * * @throw {Error} in case of empty UA String * * @constructor */ constructor(UA, skipParsing = false) { if (UA === void (0) || UA === null || UA === '') { throw new Error("UserAgent parameter can't be empty"); } this._ua = UA; /** * @typedef ParsedResult * @property {Object} browser * @property {String|undefined} [browser.name] * Browser name, like `"Chrome"` or `"Internet Explorer"` * @property {String|undefined} [browser.version] Browser version as a String `"12.01.45334.10"` * @property {Object} os * @property {String|undefined} [os.name] OS name, like `"Windows"` or `"macOS"` * @property {String|undefined} [os.version] OS version, like `"NT 5.1"` or `"10.11.1"` * @property {String|undefined} [os.versionName] OS name, like `"XP"` or `"High Sierra"` * @property {Object} platform * @property {String|undefined} [platform.type] * platform type, can be either `"desktop"`, `"tablet"` or `"mobile"` * @property {String|undefined} [platform.vendor] Vendor of the device, * like `"Apple"` or `"Samsung"` * @property {String|undefined} [platform.model] Device model, * like `"iPhone"` or `"Kindle Fire HD 7"` * @property {Object} engine * @property {String|undefined} [engine.name] * Can be any of this: `WebKit`, `Blink`, `Gecko`, `Trident`, `Presto`, `EdgeHTML` * @property {String|undefined} [engine.version] String version of the engine */ this.parsedResult = {}; if (skipParsing !== true) { this.parse(); } } /** * Get UserAgent string of current Parser instance * @return {String} User-Agent String of the current object * * @public */ getUA() { return this._ua; } /** * Test a UA string for a regexp * @param {RegExp} regex * @return {Boolean} */ test(regex) { return regex.test(this._ua); } /** * Get parsed browser object * @return {Object} */ parseBrowser() { this.parsedResult.browser = {}; const browserDescriptor = browserParsersList.find((_browser) => { if (typeof _browser.test === 'function') { return _browser.test(this); } if (_browser.test instanceof Array) { return _browser.test.some(condition => this.test(condition)); } throw new Error("Browser's test function is not valid"); }); if (browserDescriptor) { this.parsedResult.browser = browserDescriptor.describe(this.getUA()); } return this.parsedResult.browser; } /** * Get parsed browser object * @return {Object} * * @public */ getBrowser() { if (this.parsedResult.browser) { return this.parsedResult.browser; } return this.parseBrowser(); } /** * Get browser's name * @return {String} Browser's name or an empty string * * @public */ getBrowserName(toLowerCase) { if (toLowerCase) { return String(this.getBrowser().name).toLowerCase() || ''; } return this.getBrowser().name || ''; } /** * Get browser's version * @return {String} version of browser * * @public */ getBrowserVersion() { return this.getBrowser().version; } /** * Get OS * @return {Object} * * @example * this.getOS(); * { * name: 'macOS', * version: '10.11.12' * } */ getOS() { if (this.parsedResult.os) { return this.parsedResult.os; } return this.parseOS(); } /** * Parse OS and save it to this.parsedResult.os * @return {*|{}} */ parseOS() { this.parsedResult.os = {}; const os = osParsersList.find((_os) => { if (typeof _os.test === 'function') { return _os.test(this); } if (_os.test instanceof Array) { return _os.test.some(condition => this.test(condition)); } throw new Error("Browser's test function is not valid"); }); if (os) { this.parsedResult.os = os.describe(this.getUA()); } return this.parsedResult.os; } /** * Get OS name * @param {Boolean} [toLowerCase] return lower-cased value * @return {String} name of the OS — macOS, Windows, Linux, etc. */ getOSName(toLowerCase) { const { name } = this.getOS(); if (toLowerCase) { return String(name).toLowerCase() || ''; } return name || ''; } /** * Get OS version * @return {String} full version with dots ('10.11.12', '5.6', etc) */ getOSVersion() { return this.getOS().version; } /** * Get parsed platform * @return {{}} */ getPlatform() { if (this.parsedResult.platform) { return this.parsedResult.platform; } return this.parsePlatform(); } /** * Get platform name * @param {Boolean} [toLowerCase=false] * @return {*} */ getPlatformType(toLowerCase = false) { const { type } = this.getPlatform(); if (toLowerCase) { return String(type).toLowerCase() || ''; } return type || ''; } /** * Get parsed platform * @return {{}} */ parsePlatform() { this.parsedResult.platform = {}; const platform = platformParsersList.find((_platform) => { if (typeof _platform.test === 'function') { return _platform.test(this); } if (_platform.test instanceof Array) { return _platform.test.some(condition => this.test(condition)); } throw new Error("Browser's test function is not valid"); }); if (platform) { this.parsedResult.platform = platform.describe(this.getUA()); } return this.parsedResult.platform; } /** * Get parsed engine * @return {{}} */ getEngine() { if (this.parsedResult.engine) { return this.parsedResult.engine; } return this.parseEngine(); } /** * Get engines's name * @return {String} Engines's name or an empty string * * @public */ getEngineName(toLowerCase) { if (toLowerCase) { return String(this.getEngine().name).toLowerCase() || ''; } return this.getEngine().name || ''; } /** * Get parsed platform * @return {{}} */ parseEngine() { this.parsedResult.engine = {}; const engine = enginesParsersList.find((_engine) => { if (typeof _engine.test === 'function') { return _engine.test(this); } if (_engine.test instanceof Array) { return _engine.test.some(condition => this.test(condition)); } throw new Error("Browser's test function is not valid"); }); if (engine) { this.parsedResult.engine = engine.describe(this.getUA()); } return this.parsedResult.engine; } /** * Parse full information about the browser */ parse() { this.parseBrowser(); this.parseOS(); this.parsePlatform(); this.parseEngine(); return this; } /** * Get parsed result * @return {ParsedResult} */ getResult() { return Object.assign({}, this.parsedResult); } /** * Check if parsed browser matches certain conditions * * @param {Object} checkTree It's one or two layered object, * which can include a platform or an OS on the first layer * and should have browsers specs on the bottom-laying layer * * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not. * Returns `undefined` when the browser is no described in the checkTree object. * * @example * const browser = new Bowser(UA); * if (browser.satisfies({chrome: '>118.01.1322' })) * // or with os * if (browser.satisfies({windows: { chrome: '>118.01.1322' } })) * // or with platforms * if (browser.satisfies({desktop: { chrome: '>118.01.1322' } })) */ satisfies(checkTree) { const platformsAndOSes = {}; let platformsAndOSCounter = 0; const browsers = {}; let browsersCounter = 0; const allDefinitions = Object.keys(checkTree); allDefinitions.forEach((key) => { const currentDefinition = checkTree[key]; if (typeof currentDefinition === 'string') { browsers[key] = currentDefinition; browsersCounter += 1; } else if (typeof currentDefinition === 'object') { platformsAndOSes[key] = currentDefinition; platformsAndOSCounter += 1; } }); if (platformsAndOSCounter > 0) { const platformsAndOSNames = Object.keys(platformsAndOSes); const OSMatchingDefinition = platformsAndOSNames.find(name => (this.isOS(name))); if (OSMatchingDefinition) { const osResult = this.satisfies(platformsAndOSes[OSMatchingDefinition]); if (osResult !== void 0) { return osResult; } } const platformMatchingDefinition = platformsAndOSNames.find(name => (this.isPlatform(name))); if (platformMatchingDefinition) { const platformResult = this.satisfies(platformsAndOSes[platformMatchingDefinition]); if (platformResult !== void 0) { return platformResult; } } } if (browsersCounter > 0) { const browserNames = Object.keys(browsers); const matchingDefinition = browserNames.find(name => (this.isBrowser(name, true))); if (matchingDefinition !== void 0) { return this.compareVersion(browsers[matchingDefinition]); } } return undefined; } /** * Check if the browser name equals the passed string * @param browserName The string to compare with the browser name * @param [includingAlias=false] The flag showing whether alias will be included into comparison * @returns {boolean} */ isBrowser(browserName, includingAlias = false) { const defaultBrowserName = this.getBrowserName(); const possibleNames = [defaultBrowserName.toLowerCase()]; const alias = Utils.getBrowserAlias(defaultBrowserName); if (includingAlias && typeof alias !== 'undefined') { possibleNames.push(alias.toLowerCase()); } return possibleNames.indexOf(browserName.toLowerCase()) !== -1; } compareVersion(version) { let expectedResults = [0]; let comparableVersion = version; let isLoose = false; const currentBrowserVersion = this.getBrowserVersion(); if (typeof currentBrowserVersion !== 'string') { return void 0; } if (version[0] === '>' || version[0] === '<') { comparableVersion = version.substr(1); if (version[1] === '=') { isLoose = true; comparableVersion = version.substr(2); } else { expectedResults = []; } if (version[0] === '>') { expectedResults.push(1); } else { expectedResults.push(-1); } } else if (version[0] === '=') { comparableVersion = version.substr(1); } else if (version[0] === '~') { isLoose = true; comparableVersion = version.substr(1); } return expectedResults.indexOf( Utils.compareVersions(currentBrowserVersion, comparableVersion, isLoose), ) > -1; } isOS(osName) { return this.getOSName(true) === String(osName).toLowerCase(); } isPlatform(platformType) { return this.getPlatformType(true) === String(platformType).toLowerCase(); } isEngine(engineName) { return this.getEngineName(true) === String(engineName).toLowerCase(); } /** * Is anything? Check if the browser is called "anything", * the OS called "anything" or the platform called "anything" * @param {String} anything * @returns {Boolean} */ is(anything) { return this.isBrowser(anything) || this.isOS(anything) || this.isPlatform(anything); } /** * Check if any of the given values satisfies this.is(anything) * @param {String[]} anythings * @returns {Boolean} */ some(anythings = []) { return anythings.some(anything => this.is(anything)); } } export default Parser;