monacousa-portal/utils/static-device-detection.ts

129 lines
3.6 KiB
TypeScript

/**
* Static Device Detection Utility
* Provides non-reactive device detection for Safari iOS reload loop prevention
* Uses direct navigator.userAgent analysis without creating Vue reactive dependencies
*/
export interface DeviceInfo {
isMobile: boolean;
isIos: boolean;
isSafari: boolean;
isMobileSafari: boolean;
isAndroid: boolean;
userAgent: string;
}
let cachedDeviceInfo: DeviceInfo | null = null;
/**
* Get static device information without creating reactive dependencies
* Results are cached to prevent multiple userAgent parsing
*/
export function getStaticDeviceInfo(): DeviceInfo {
// Return cached result if available
if (cachedDeviceInfo) {
return cachedDeviceInfo;
}
// Only run on client-side
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
cachedDeviceInfo = {
isMobile: false,
isIos: false,
isSafari: false,
isMobileSafari: false,
isAndroid: false,
userAgent: ''
};
return cachedDeviceInfo;
}
const userAgent = navigator.userAgent;
// Device detection logic
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
const isIos = /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
const isAndroid = /Android/i.test(userAgent);
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const isMobileSafari = isIos && isSafari;
// Cache the result
cachedDeviceInfo = {
isMobile,
isIos,
isSafari,
isMobileSafari,
isAndroid,
userAgent
};
return cachedDeviceInfo;
}
/**
* Get CSS classes for device-specific styling
* Returns a space-separated string of CSS classes
*/
export function getDeviceCssClasses(baseClass: string = ''): string {
const device = getStaticDeviceInfo();
const classes = [baseClass].filter(Boolean);
if (device.isMobile) classes.push('is-mobile');
if (device.isIos) classes.push('is-ios');
if (device.isSafari) classes.push('is-safari');
if (device.isMobileSafari) classes.push('is-mobile-safari');
if (device.isAndroid) classes.push('is-android');
return classes.join(' ');
}
/**
* Check if current device is mobile Safari specifically
* This is the primary problematic browser for reload loops
*/
export function isMobileSafari(): boolean {
return getStaticDeviceInfo().isMobileSafari;
}
/**
* Apply mobile Safari specific optimizations to DOM element
* Should be called once per component to prevent reactive updates
*/
export function applyMobileSafariOptimizations(element?: HTMLElement): void {
if (!isMobileSafari()) return;
const targetElement = element || document.documentElement;
// Apply performance optimization classes
targetElement.classList.add('is-mobile-safari', 'performance-optimized');
// Set viewport height CSS variable for mobile Safari
const vh = window.innerHeight * 0.01;
targetElement.style.setProperty('--vh', `${vh}px`);
// Disable problematic CSS features for performance
targetElement.style.setProperty('--backdrop-filter', 'none');
targetElement.style.setProperty('--will-change', 'auto');
}
/**
* Get viewport meta content optimized for mobile Safari
*/
export function getMobileSafariViewportMeta(): string {
const device = getStaticDeviceInfo();
if (device.isMobileSafari) {
// Prevent zoom on input focus for iOS Safari
return 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
}
return 'width=device-width, initial-scale=1.0';
}
/**
* Clear cached device info (useful for testing)
*/
export function clearDeviceInfoCache(): void {
cachedDeviceInfo = null;
}