//copied from https://github.com/faisalman/ua-parser-js/blob/master/src/ua-parser.js const EMPTY = '', UNKNOWN = '?', FUNC_TYPE = 'function', UNDEF_TYPE = 'undefined', OBJ_TYPE = 'object', STR_TYPE = 'string', NAME = 'name', VERSION = 'version'; const BROWSER = 'Browser', CHROME = 'Chrome', FIREFOX = 'Firefox', OPERA = 'Opera'; const oldSafariMap = { '1.0': '/8', '1.2': '/1', '1.3': '/3', '2.0': '/412', '2.0.2': '/416', '2.0.3': '/417', '2.0.4': '/419', '?': '/', }; function strMapper(str, map) { for (const i in map) { // check if current value is array if (typeof map[i] === OBJ_TYPE && map[i].length > 0) { for (const j in map[i]) { if (j.includes(str)) { return i === UNKNOWN ? undefined : i; } } } else if (map[i].includes(str)) { return i === UNKNOWN ? undefined : i; } } return str; } function majorize(version) { return typeof version === STR_TYPE ? version.replace(/[^\d.]/g, EMPTY).split('.')[0] : undefined; } function rgxMapper(browser, ua, arrays) { let i = 0, j, k, p, q, matches, match; // loop through all regexes maps while (i < arrays.length && !matches) { const regex = arrays[i], // even sequence (0,2,4,..) props = arrays[i + 1]; // odd sequence (1,3,5,..) j = k = 0; // try matching uastring with regexes while (j < regex.length && !matches) { matches = regex[j++].exec(ua); if (matches) { for (p = 0; p < props.length; p++) { match = matches[++k]; q = props[p]; // check if given property is actually array if (typeof q === OBJ_TYPE && q.length > 0) { if (q.length == 2) { if (typeof q[1] == FUNC_TYPE) { // assign modified match browser[q[0]] = q[1].call(browser, match); } else { // assign given value, ignore regex match browser[q[0]] = q[1]; } } else if (q.length == 3) { // check whether function or regex if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) { // call function (usually string mapper) browser[q[0]] = match ? q[1].call(browser, match, q[2]) : undefined; } else { // sanitize match using given regex browser[q[0]] = match ? match.replace(q[1], q[2]) : undefined; } } else if (q.length == 4) { browser[q[0]] = match ? q[3].call(browser, match.replace(q[1], q[2])) : undefined; } } else { browser[q] = match ? match : undefined; } } } } i += 2; } } const browserRegexes = [ [ /\b(?:crmo|crios)\/([\w.]+)/i, // Chrome for Android/iOS ], [VERSION, [NAME, 'Chrome']], [ /edg(?:e|ios|a)?\/([\w.]+)/i, // Microsoft Edge ], [VERSION, [NAME, 'Edge']], [ // Presto based /(opera mini)\/([-\w.]+)/i, // Opera Mini /(opera [mobiletab]{3,6})\b.+version\/([-\w.]+)/i, // Opera Mobi/Tablet /(opera)(?:.+version\/|[/ ]+)([\w.]+)/i, // Opera ], [NAME, VERSION], [ /opios[/ ]+([\w.]+)/i, // Opera mini on iphone >= 8.0 ], [VERSION, [NAME, OPERA + ' Mini']], [ /\bopr\/([\w.]+)/i, // Opera Webkit ], [VERSION, [NAME, OPERA]], [ // Mixed /(kindle)\/([\w.]+)/i, // Kindle /(lunascape|maxthon|netfront|jasmine|blazer)[/ ]?([\w.]*)/i, // Lunascape/Maxthon/Netfront/Jasmine/Blazer // Trident based /(avant |iemobile|slim)(?:browser)?[/ ]?([\w.]*)/i, // Avant/IEMobile/SlimBrowser /(ba?idubrowser)[/ ]?([\w.]+)/i, // Baidu Browser /(?:ms|\()(ie) ([\w.]+)/i, // Internet Explorer // Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon /(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon|rekonq|puffin|brave|whale|qqbrowserlite|qq)\/([-\w.]+)/i, // Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ, aka ShouQ /(weibo)__([\d.]+)/i, // Weibo ], [NAME, VERSION], [ /(?:\buc? ?browser|(?:juc.+)ucweb)[/ ]?([\w.]+)/i, // UCBrowser ], [VERSION, [NAME, 'UC' + BROWSER]], [ /\bqbcore\/([\w.]+)/i, // WeChat Desktop for Windows Built-in Browser ], [VERSION, [NAME, 'WeChat(Win) Desktop']], [ /micromessenger\/([\w.]+)/i, // WeChat ], [VERSION, [NAME, 'WeChat']], [ /konqueror\/([\w.]+)/i, // Konqueror ], [VERSION, [NAME, 'Konqueror']], [ /trident.+rv[: ]([\w.]{1,9})\b.+like gecko/i, // IE11 ], [VERSION, [NAME, 'IE']], [ /yabrowser\/([\w.]+)/i, // Yandex ], [VERSION, [NAME, 'Yandex']], [ /(avast|avg)\/([\w.]+)/i, // Avast/AVG Secure Browser ], [[NAME, /(.+)/, '$1 Secure ' + BROWSER], VERSION], [ /\bfocus\/([\w.]+)/i, // Firefox Focus ], [VERSION, [NAME, FIREFOX + ' Focus']], [ /\bopt\/([\w.]+)/i, // Opera Touch ], [VERSION, [NAME, OPERA + ' Touch']], [ /coc_coc\w+\/([\w.]+)/i, // Coc Coc Browser ], [VERSION, [NAME, 'Coc Coc']], [ /dolfin\/([\w.]+)/i, // Dolphin ], [VERSION, [NAME, 'Dolphin']], [ /coast\/([\w.]+)/i, // Opera Coast ], [VERSION, [NAME, OPERA + ' Coast']], [ /miuibrowser\/([\w.]+)/i, // MIUI Browser ], [VERSION, [NAME, 'MIUI ' + BROWSER]], [ /fxios\/([-\w.]+)/i, // Firefox for iOS ], [VERSION, [NAME, FIREFOX]], [ /\bqihu|(qi?ho?o?|360)browser/i, // 360 ], [[NAME, '360 ' + BROWSER]], [/(oculus|samsung|sailfish)browser\/([\w.]+)/i], [[NAME, /(.+)/, '$1 ' + BROWSER], VERSION], [ // Oculus/Samsung/Sailfish Browser /(comodo_dragon)\/([\w.]+)/i, // Comodo Dragon ], [[NAME, /_/g, ' '], VERSION], [ /(electron)\/([\w.]+) safari/i, // Electron-based App /(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w.]+))/i, // Tesla /m?(qqbrowser|baiduboxapp|2345Explorer)[/ ]?([\w.]+)/i, // QQBrowser/Baidu App/2345 Browser ], [NAME, VERSION], [ /(metasr)[/ ]?([\w.]+)/i, // SouGouBrowser /(lbbrowser)/i, // LieBao Browser ], [NAME], [ // WebView /((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w.]+);)/i, // Facebook App for iOS & Android ], [[NAME, 'Facebook'], VERSION], [ /safari (line)\/([\w.]+)/i, // Line App for iOS /\b(line)\/([\w.]+)\/iab/i, // Line App for Android /(chromium|instagram)[/ ]([-\w.]+)/i, // Chromium/Instagram ], [NAME, VERSION], [ /\bgsa\/([\w.]+) .*safari\//i, // Google Search Appliance on iOS ], [VERSION, [NAME, 'GSA']], [ /headlesschrome(?:\/([\w.]+)| )/i, // Chrome Headless ], [VERSION, [NAME, CHROME + ' Headless']], [ / wv\).+(chrome)\/([\w.]+)/i, // Chrome WebView ], [[NAME, CHROME + ' WebView'], VERSION], [ /droid.+ version\/([\w.]+)\b.+(?:mobile safari|safari)/i, // Android Browser ], [VERSION, [NAME, 'Android ' + BROWSER]], [ /(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w.]+)/i, // Chrome/OmniWeb/Arora/Tizen/Nokia ], [NAME, VERSION], [ /version\/([\w.]+) .*mobile\/\w+ (safari)/i, // Mobile Safari ], [VERSION, [NAME, 'Mobile Safari']], [ /version\/([\w.]+) .*(mobile ?safari|safari)/i, // Safari & Safari Mobile ], [VERSION, NAME], [ /webkit.+?(mobile ?safari|safari)(\/[\w.]+)/i, // Safari < 3.0 ], [NAME, [VERSION, strMapper, oldSafariMap]], [/(webkit|khtml)\/([\w.]+)/i], [NAME, VERSION], [ // Gecko based /(navigator|netscape\d?)\/([-\w.]+)/i, // Netscape ], [[NAME, 'Netscape'], VERSION], [ /mobile vr; rv:([\w.]+)\).+firefox/i, // Firefox Reality ], [VERSION, [NAME, FIREFOX + ' Reality']], [ /ekiohf.+(flow)\/([\w.]+)/i, // Flow /(swiftfox)/i, // Swiftfox /(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror|klar)[/ ]?([\w.+]+)/i, // IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror/Klar /(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w.]+)$/i, // Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix /(firefox)\/([\w.]+)/i, // Other Firefox-based /(mozilla)\/([\w.]+) .+rv:.+gecko\/\d+/i, // Mozilla // Other /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir|obigo|mosaic|(?:go|ice|up)[. ]?browser)[-/ ]?v?([\w.]+)/i, // Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir/Obigo/Mosaic/Go/ICE/UP.Browser /(links) \(([\w.]+)/i, // Links ], [NAME, VERSION], ]; const _ua = typeof window !== UNDEF_TYPE && window.navigator && window.navigator.userAgent ? window.navigator.userAgent : EMPTY; interface Browser { name?: string; version?: string; } export function evaluateBrowserVersion(): Browser { const browser: Browser = { name: '', version: '' }; rgxMapper(browser, _ua, browserRegexes); browser.version = majorize(browser.version); return browser; } type Mobile = RegExpMatchArray | null; export const isMobile = { Android: function (): Mobile { return navigator.userAgent.match(/Android/i); }, BlackBerry: function (): Mobile { return navigator.userAgent.match(/BlackBerry/i); }, iOS: function (): Mobile { return navigator.userAgent.match(/iPhone|iPad|iPod/i); }, Opera: function (): Mobile { return navigator.userAgent.match(/Opera Mini/i); }, Windows: function (): Mobile { return ( navigator.userAgent.match(/IEMobile/i) || navigator.userAgent.match(/WPDesktop/i) ); }, any: function (): Mobile { return ( isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows() ); }, }; export default evaluateBrowserVersion;