191 lines
6.8 KiB
TypeScript
191 lines
6.8 KiB
TypeScript
/**
|
|
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
|
*/
|
|
|
|
type NavigatorUAData = {
|
|
brands?: {brand: string; version: string}[];
|
|
mobile?: boolean;
|
|
platform?: string;
|
|
getHighEntropyValues?: (hints: string[]) => Promise<Record<string, string>>;
|
|
toJSON?: () => object;
|
|
};
|
|
|
|
export default class PlatformDetectionService {
|
|
private static readonly _userAgent: string = globalThis.navigator?.userAgent ?? '';
|
|
// @ts-expect-error NavigatorUAData is experimental and not defined in the lib dom yet https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
private static readonly _userAgentData: NavigatorUAData | undefined = globalThis.navigator?.userAgentData;
|
|
|
|
private static readonly _areClientHintsSupported: boolean = !!PlatformDetectionService._userAgentData;
|
|
private static _platform: string = 'Unknown';
|
|
private static _platformVersion: string = '';
|
|
private static _browserName: string = 'Unknown';
|
|
private static _browserVersion: string = '?';
|
|
private static _isWebview: boolean = false;
|
|
|
|
static {
|
|
if (PlatformDetectionService._areClientHintsSupported) {
|
|
PlatformDetectionService.initFromClientHints();
|
|
} else {
|
|
PlatformDetectionService.initFromUserAgent();
|
|
}
|
|
}
|
|
|
|
private constructor() {
|
|
throw new Error('PlatformDetectionService is a static class that may not be instantiated');
|
|
}
|
|
|
|
// ---- Public API ----
|
|
static get platform(): string {
|
|
return PlatformDetectionService._platform;
|
|
}
|
|
static get platformVersion(): string {
|
|
return PlatformDetectionService._platformVersion;
|
|
}
|
|
static get userAgent(): string {
|
|
return PlatformDetectionService._userAgent;
|
|
}
|
|
static get browserName(): string {
|
|
return PlatformDetectionService._browserName;
|
|
}
|
|
static get browserVersion(): string {
|
|
return PlatformDetectionService._browserVersion;
|
|
}
|
|
static get isWebview(): boolean {
|
|
return PlatformDetectionService._isWebview;
|
|
}
|
|
static get areClientHintsSupported(): boolean {
|
|
return PlatformDetectionService._areClientHintsSupported;
|
|
}
|
|
|
|
/**
|
|
* Optional async initialization for high-entropy values like platformVersion
|
|
*/
|
|
static async initAsync(): Promise<void> {
|
|
if (PlatformDetectionService._areClientHintsSupported && PlatformDetectionService._userAgentData?.getHighEntropyValues) {
|
|
const values = await PlatformDetectionService._userAgentData.getHighEntropyValues(['platformVersion']);
|
|
|
|
if (values.platformVersion) {
|
|
PlatformDetectionService._platformVersion = values.platformVersion;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- Init strategies ----
|
|
private static initFromClientHints() {
|
|
const data = PlatformDetectionService._userAgentData as NavigatorUAData;
|
|
const nonChromiumBrand = data.brands?.find(b => b.brand !== 'Chromium');
|
|
|
|
PlatformDetectionService._browserName = nonChromiumBrand?.brand ?? 'Unknown';
|
|
PlatformDetectionService._browserVersion = nonChromiumBrand?.version ?? '?';
|
|
PlatformDetectionService._platform = data.platform ?? 'Unknown';
|
|
PlatformDetectionService._isWebview = PlatformDetectionService.extractIsWebviewFromUserAgent(); // Fallback check
|
|
}
|
|
|
|
private static initFromUserAgent() {
|
|
PlatformDetectionService._platform = PlatformDetectionService.extractPlatformFromUserAgent();
|
|
PlatformDetectionService._platformVersion = PlatformDetectionService.extractPlatformVersionFromUserAgent();
|
|
PlatformDetectionService._browserName = PlatformDetectionService.extractBrowserNameFromUserAgent();
|
|
PlatformDetectionService._browserVersion = PlatformDetectionService.extractBrowserVersionFromUserAgent();
|
|
PlatformDetectionService._isWebview = PlatformDetectionService.extractIsWebviewFromUserAgent();
|
|
}
|
|
|
|
// ---- Helpers ----
|
|
private static extractBrowserNameFromUserAgent(): string {
|
|
if (/Edg\//.test(PlatformDetectionService._userAgent)) {
|
|
return 'Edge';
|
|
}
|
|
|
|
if (/OPR\//.test(PlatformDetectionService._userAgent)) {
|
|
return 'Opera';
|
|
}
|
|
|
|
if (/Firefox\//.test(PlatformDetectionService._userAgent)) {
|
|
return 'Firefox';
|
|
}
|
|
|
|
if (/Trident\/.*rv:/.test(PlatformDetectionService._userAgent)) {
|
|
return 'IE';
|
|
}
|
|
|
|
if (/Chrome\//.test(PlatformDetectionService._userAgent)) {
|
|
return 'Chrome';
|
|
}
|
|
|
|
if (/Safari\//.test(PlatformDetectionService._userAgent)) {
|
|
return 'Safari';
|
|
}
|
|
|
|
if (/ReactNative\//.test(PlatformDetectionService._userAgent)) {
|
|
return 'ReactNative';
|
|
}
|
|
|
|
return 'Unknown';
|
|
}
|
|
|
|
private static extractBrowserVersionFromUserAgent(): string {
|
|
return (
|
|
PlatformDetectionService.matchVersion(/Edg\/([\d.]+)/) ??
|
|
PlatformDetectionService.matchVersion(/OPR\/([\d.]+)/) ??
|
|
PlatformDetectionService.matchVersion(/Firefox\/([\d.]+)/) ??
|
|
PlatformDetectionService.matchVersion(/rv:([\d.]+)/) ?? // IE
|
|
PlatformDetectionService.matchVersion(/Chrome\/([\d.]+)/) ??
|
|
PlatformDetectionService.matchVersion(/Version\/([\d.]+)/) ?? // Safari often uses "Version/"
|
|
PlatformDetectionService.matchVersion(/Safari\/([\d.]+)/) ??
|
|
PlatformDetectionService.matchVersion(/ReactNative\/([\d.]+)/) ??
|
|
'?'
|
|
);
|
|
}
|
|
|
|
private static extractPlatformFromUserAgent(): string {
|
|
if (/Windows/.test(PlatformDetectionService._userAgent)) {
|
|
return 'Windows';
|
|
}
|
|
|
|
if (/iPhone|iPad|iPod/.test(PlatformDetectionService._userAgent)) {
|
|
return 'iOS';
|
|
}
|
|
|
|
if (/Mac OS X/.test(PlatformDetectionService._userAgent)) {
|
|
return 'macOS';
|
|
}
|
|
|
|
if (/Android/.test(PlatformDetectionService._userAgent)) {
|
|
return 'Android';
|
|
}
|
|
|
|
if (/Linux/.test(PlatformDetectionService._userAgent)) {
|
|
return 'Linux';
|
|
}
|
|
|
|
return 'Unknown';
|
|
}
|
|
|
|
private static extractPlatformVersionFromUserAgent(): string {
|
|
switch (PlatformDetectionService._platform) {
|
|
case 'Windows':
|
|
return PlatformDetectionService.matchVersion(/Windows NT ([\d.]+)/) ?? '';
|
|
case 'iOS':
|
|
return PlatformDetectionService.matchVersion(/OS ([\d_]+)/)?.replace(/_/g, '.') ?? '';
|
|
case 'macOS':
|
|
return PlatformDetectionService.matchVersion(/Mac OS X ([\d_]+)/)?.replace(/_/g, '.') ?? '';
|
|
case 'Android':
|
|
return PlatformDetectionService.matchVersion(/Android ([\d.]+)/) ?? '';
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
private static extractIsWebviewFromUserAgent(): boolean {
|
|
return (
|
|
/; wv/.test(PlatformDetectionService._userAgent) || // Android webview
|
|
(/Android/.test(PlatformDetectionService._userAgent) && /Version\/[\d.]+/.test(PlatformDetectionService._userAgent)) || // Some Android webviews
|
|
/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/.test(PlatformDetectionService._userAgent) // IOS webview
|
|
);
|
|
}
|
|
|
|
private static matchVersion(pattern: RegExp): string | null {
|
|
const match = PlatformDetectionService._userAgent.match(pattern);
|
|
|
|
return match ? match[1] : null;
|
|
}
|
|
} |