Fix browser detection overwrites (#7411)
* Fix browser detection overwrites * Fix Opera TV detection * Move tv flag logic * Fix indentation
This commit is contained in:
2
src/scripts/browser.d.ts
vendored
2
src/scripts/browser.d.ts
vendored
@@ -37,4 +37,6 @@ declare namespace browser {
|
||||
export let iOSVersion: number | undefined;
|
||||
}
|
||||
|
||||
export function detectBrowser(userAgent?: string): browser;
|
||||
|
||||
export default browser;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
function isTv() {
|
||||
function isTv(userAgent) {
|
||||
// This is going to be really difficult to get right
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
|
||||
// The OculusBrowsers userAgent also has the samsungbrowser defined but is not a tv.
|
||||
if (userAgent.includes('oculusbrowser')) {
|
||||
@@ -23,12 +22,10 @@ function isTv() {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isWeb0s();
|
||||
return isWeb0s(userAgent);
|
||||
}
|
||||
|
||||
function isWeb0s() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
|
||||
function isWeb0s(userAgent) {
|
||||
return userAgent.includes('netcast')
|
||||
|| userAgent.includes('web0s');
|
||||
}
|
||||
@@ -46,10 +43,8 @@ function isMobile(userAgent) {
|
||||
'opera mini'
|
||||
];
|
||||
|
||||
const lower = userAgent.toLowerCase();
|
||||
|
||||
for (let i = 0, length = terms.length; i < length; i++) {
|
||||
if (lower.includes(terms[i])) {
|
||||
for (const term of terms) {
|
||||
if (userAgent.includes(term)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -191,8 +186,7 @@ function supportsCssAnimation(allowPrefix) {
|
||||
}
|
||||
|
||||
const uaMatch = function (ua) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
// Motorola Edge device UA triggers false positive for Edge browser
|
||||
ua = ua.replace(/(motorola edge)/, '').trim();
|
||||
|
||||
const match = /(edg)[ /]([\w.]+)/.exec(ua)
|
||||
@@ -248,99 +242,98 @@ const uaMatch = function (ua) {
|
||||
};
|
||||
};
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
export const detectBrowser = (userAgent = navigator.userAgent) => {
|
||||
const normalizedUA = userAgent.toLowerCase();
|
||||
|
||||
const matched = uaMatch(userAgent);
|
||||
const browser = {};
|
||||
const matched = uaMatch(normalizedUA);
|
||||
const browser = {};
|
||||
|
||||
if (matched.browser) {
|
||||
browser[matched.browser] = true;
|
||||
browser.version = matched.version;
|
||||
browser.versionMajor = matched.versionMajor;
|
||||
}
|
||||
|
||||
if (matched.platform) {
|
||||
browser[matched.platform] = true;
|
||||
}
|
||||
|
||||
browser.edgeChromium = browser.edg || browser.edga || browser.edgios;
|
||||
|
||||
if (!browser.chrome && !browser.edgeChromium && !browser.edge && !browser.opera && userAgent.toLowerCase().includes('webkit')) {
|
||||
browser.safari = true;
|
||||
}
|
||||
|
||||
browser.osx = userAgent.toLowerCase().includes('mac os x');
|
||||
|
||||
// This is a workaround to detect iPads on iOS 13+ that report as desktop Safari
|
||||
// This may break in the future if Apple releases a touchscreen Mac
|
||||
// https://forums.developer.apple.com/thread/119186
|
||||
if (browser.osx && !browser.iphone && !browser.ipod && !browser.ipad && navigator.maxTouchPoints > 1) {
|
||||
browser.ipad = true;
|
||||
}
|
||||
|
||||
if (userAgent.toLowerCase().includes('playstation 4')) {
|
||||
browser.ps4 = true;
|
||||
browser.tv = true;
|
||||
}
|
||||
|
||||
if (isMobile(userAgent)) {
|
||||
browser.mobile = true;
|
||||
}
|
||||
|
||||
if (userAgent.toLowerCase().includes('xbox')) {
|
||||
browser.xboxOne = true;
|
||||
browser.tv = true;
|
||||
}
|
||||
browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null;
|
||||
browser.hisense = userAgent.toLowerCase().includes('hisense');
|
||||
browser.tizen = userAgent.toLowerCase().includes('tizen') || window.tizen != null;
|
||||
browser.vidaa = userAgent.toLowerCase().includes('vidaa');
|
||||
browser.web0s = isWeb0s();
|
||||
browser.edgeUwp = (browser.edge || browser.edgeChromium) && (userAgent.toLowerCase().includes('msapphost') || userAgent.toLowerCase().includes('webview'));
|
||||
|
||||
if (browser.web0s) {
|
||||
browser.web0sVersion = web0sVersion(browser);
|
||||
|
||||
// UserAgent string contains 'Chrome' and 'Safari', but we only want 'web0s' to be true
|
||||
delete browser.chrome;
|
||||
delete browser.safari;
|
||||
} else if (browser.tizen) {
|
||||
const v = RegExp(/Tizen (\d+).(\d+)/).exec(userAgent);
|
||||
browser.tizenVersion = parseInt(v[1], 10) + parseInt(v[2], 10) / 10;
|
||||
|
||||
// UserAgent string contains 'Chrome' and 'Safari', but we only want 'tizen' to be true
|
||||
delete browser.chrome;
|
||||
delete browser.safari;
|
||||
} else if (browser.titanos) {
|
||||
// UserAgent string contains 'Opr' and 'Safari', but we only want 'titanos' to be true
|
||||
delete browser.operaTv;
|
||||
delete browser.safari;
|
||||
} else {
|
||||
browser.orsay = userAgent.toLowerCase().includes('smarthub');
|
||||
}
|
||||
|
||||
browser.tv = isTv();
|
||||
browser.operaTv = browser.tv && userAgent.toLowerCase().includes('opr/');
|
||||
|
||||
if (browser.mobile || browser.tv) {
|
||||
browser.slow = true;
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
|
||||
browser.touch = true;
|
||||
}
|
||||
|
||||
browser.keyboard = hasKeyboard(browser);
|
||||
browser.supportsCssAnimation = supportsCssAnimation;
|
||||
|
||||
browser.iOS = browser.ipad || browser.iphone || browser.ipod;
|
||||
|
||||
if (browser.iOS) {
|
||||
browser.iOSVersion = iOSversion();
|
||||
|
||||
if (browser.iOSVersion && browser.iOSVersion.length >= 2) {
|
||||
browser.iOSVersion = browser.iOSVersion[0] + (browser.iOSVersion[1] / 10);
|
||||
if (matched.browser) {
|
||||
browser[matched.browser] = true;
|
||||
browser.version = matched.version;
|
||||
browser.versionMajor = matched.versionMajor;
|
||||
}
|
||||
}
|
||||
|
||||
export default browser;
|
||||
if (matched.platform) {
|
||||
browser[matched.platform] = true;
|
||||
}
|
||||
|
||||
browser.edgeChromium = browser.edg || browser.edga || browser.edgios;
|
||||
|
||||
if (!browser.chrome && !browser.edgeChromium && !browser.edge && !browser.opera && normalizedUA.includes('webkit')) {
|
||||
browser.safari = true;
|
||||
}
|
||||
|
||||
browser.osx = normalizedUA.includes('mac os x');
|
||||
|
||||
// This is a workaround to detect iPads on iOS 13+ that report as desktop Safari
|
||||
// This may break in the future if Apple releases a touchscreen Mac
|
||||
// https://forums.developer.apple.com/thread/119186
|
||||
if (browser.osx && !browser.iphone && !browser.ipod && !browser.ipad && navigator.maxTouchPoints > 1) {
|
||||
browser.ipad = true;
|
||||
}
|
||||
|
||||
if (isMobile(normalizedUA)) {
|
||||
browser.mobile = true;
|
||||
}
|
||||
|
||||
browser.ps4 = normalizedUA.includes('playstation 4');
|
||||
browser.xboxOne = normalizedUA.includes('xbox');
|
||||
|
||||
browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null;
|
||||
browser.hisense = normalizedUA.includes('hisense');
|
||||
browser.tizen = normalizedUA.includes('tizen') || window.tizen != null;
|
||||
browser.vidaa = normalizedUA.includes('vidaa');
|
||||
browser.web0s = isWeb0s(normalizedUA);
|
||||
|
||||
browser.tv = browser.ps4 || browser.xboxOne || isTv(normalizedUA);
|
||||
browser.operaTv = browser.tv && normalizedUA.includes('opr/');
|
||||
|
||||
browser.edgeUwp = (browser.edge || browser.edgeChromium) && (normalizedUA.includes('msapphost') || normalizedUA.includes('webview'));
|
||||
|
||||
if (browser.web0s) {
|
||||
browser.web0sVersion = web0sVersion(browser);
|
||||
|
||||
// UserAgent string contains 'Chrome' and 'Safari', but we only want 'web0s' to be true
|
||||
delete browser.chrome;
|
||||
delete browser.safari;
|
||||
} else if (browser.tizen) {
|
||||
const v = RegExp(/Tizen (\d+).(\d+)/).exec(userAgent);
|
||||
browser.tizenVersion = parseInt(v[1], 10) + parseInt(v[2], 10) / 10;
|
||||
|
||||
// UserAgent string contains 'Chrome' and 'Safari', but we only want 'tizen' to be true
|
||||
delete browser.chrome;
|
||||
delete browser.safari;
|
||||
} else if (browser.titanos) {
|
||||
// UserAgent string contains 'Opr' and 'Safari', but we only want 'titanos' to be true
|
||||
delete browser.operaTv;
|
||||
delete browser.safari;
|
||||
} else {
|
||||
browser.orsay = normalizedUA.includes('smarthub');
|
||||
}
|
||||
|
||||
if (browser.mobile || browser.tv) {
|
||||
browser.slow = true;
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
|
||||
browser.touch = true;
|
||||
}
|
||||
|
||||
browser.keyboard = hasKeyboard(browser);
|
||||
browser.supportsCssAnimation = supportsCssAnimation;
|
||||
|
||||
browser.iOS = browser.ipad || browser.iphone || browser.ipod;
|
||||
|
||||
if (browser.iOS) {
|
||||
browser.iOSVersion = iOSversion();
|
||||
|
||||
if (browser.iOSVersion && browser.iOSVersion.length >= 2) {
|
||||
browser.iOSVersion = browser.iOSVersion[0] + (browser.iOSVersion[1] / 10);
|
||||
}
|
||||
}
|
||||
|
||||
return browser;
|
||||
};
|
||||
|
||||
export default detectBrowser();
|
||||
|
||||
28
src/scripts/browser.test.ts
Normal file
28
src/scripts/browser.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { detectBrowser } from './browser';
|
||||
|
||||
describe('Browser', () => {
|
||||
it('should identify TitanOS devices', async () => {
|
||||
// Ref: https://docs.titanos.tv/user-agents-specifications
|
||||
// Philips example
|
||||
let browser = detectBrowser('Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.4147.62 Safari/537.36 OPR/46.0.2207.0 OMI/4.24, TV_NT72690_2025_4K /<SW version> (Philips, <CTN>, wired) CE-HTML/1.0 NETTV/4.6.0.8 SignOn/2.0 SmartTvA/5.0.0 TitanOS/3.0 en Ginga');
|
||||
expect(browser.titanos).toBe(true);
|
||||
expect(browser.operaTv).toBeFalsy();
|
||||
expect(browser.safari).toBeFalsy();
|
||||
expect(browser.tv).toBe(true);
|
||||
|
||||
// JVC example
|
||||
browser = detectBrowser('Mozilla/5.0 (Linux ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.128 Safari/537.36 OMI/4.24.3.93.MIKE.227 Model/Vestel-MB190 VSTVB MB100 FVC/9.0 (VESTEL; MB190; ) HbbTV/1.7.1 (+DRM; VESTEL; MB190; 0.9.0.0; ; _TV__2025;) TitanOS/3.0 (Vestel MB190 VESTEL) SmartTvA/3.0.0');
|
||||
expect(browser.titanos).toBe(true);
|
||||
expect(browser.operaTv).toBeFalsy();
|
||||
expect(browser.safari).toBeFalsy();
|
||||
expect(browser.tv).toBe(true);
|
||||
});
|
||||
|
||||
it('should identify Xbox devices', async () => {
|
||||
const browser = detectBrowser('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0 WebView2 Xbox');
|
||||
expect(browser.xboxOne).toBe(true);
|
||||
expect(browser.tv).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user