import { UAParser } from "ua-parser-js"; const ONEPLUS_KEYBOARD_FALLBACK_MODELS = new Set(["PKC110", "PLB110"]); export type PlatformName = "ios" | "android" | "desktop" | "unknown"; export interface PlatformInfo { platform: PlatformName; osName: string; osVersion: string; deviceType: string; deviceVendor: string; deviceModel: string; cpuArchitecture: string; isMobile: boolean; isTablet: boolean; isDesktop: boolean; isIOS: boolean; isAndroid: boolean; userAgent: string; } /** * 平台 / OS / 设备检测 * * 与浏览器内核、内置浏览器判断解耦;浏览器相关能力请使用 `BrowserDetector`。 */ export class PlatformDetector { private constructor() {} static getPlatformInfo(ua: string | null = null): PlatformInfo { const userAgent = PlatformDetector.getUserAgent(ua); const result = new UAParser(userAgent).getResult(); const osName = result.os.name ?? ""; const deviceType = result.device.type ?? ""; const isIOS = PlatformDetector.isIOS(userAgent); const isAndroid = PlatformDetector.isAndroid(userAgent); const isTablet = deviceType === "tablet" || PlatformDetector.isIPadOS(); const isMobile = deviceType === "mobile" || isTablet; return { platform: PlatformDetector.getPlatform(userAgent), osName, osVersion: result.os.version ?? "", deviceType, deviceVendor: result.device.vendor ?? "", deviceModel: result.device.model ?? "", cpuArchitecture: result.cpu.architecture ?? "", isMobile, isTablet, isDesktop: !isMobile, isIOS, isAndroid, userAgent, }; } static getPlatform(ua: string | null = null): PlatformName { const userAgent = PlatformDetector.getUserAgent(ua); if (PlatformDetector.isIOS(userAgent)) return "ios"; if (PlatformDetector.isAndroid(userAgent)) return "android"; if (userAgent.length > 0) return "desktop"; return "unknown"; } static isIOS(ua: string | null = null): boolean { const userAgent = PlatformDetector.getUserAgent(ua); const osName = new UAParser(userAgent).getOS().name ?? ""; return /iOS/i.test(osName) || PlatformDetector.isIPadOS(); } static isAndroid(ua: string | null = null): boolean { const userAgent = PlatformDetector.getUserAgent(ua); const osName = new UAParser(userAgent).getOS().name ?? ""; return /Android/i.test(osName) || /Android/i.test(userAgent); } static isMobile(ua: string | null = null): boolean { return PlatformDetector.getPlatformInfo(ua).isMobile; } static isTablet(ua: string | null = null): boolean { return PlatformDetector.getPlatformInfo(ua).isTablet; } static isDesktop(ua: string | null = null): boolean { return PlatformDetector.getPlatformInfo(ua).isDesktop; } static isXiaomiFamilyDevice(ua: string | null = null): boolean { const info = PlatformDetector.getPlatformInfo(ua); const target = `${info.deviceVendor} ${info.deviceModel} ${info.userAgent}`; return /\b(Xiaomi|Redmi|POCO|Mi\s|MIX|MIUI)\b/i.test(target); } static isOppoFamilyDevice(ua: string | null = null): boolean { const info = PlatformDetector.getPlatformInfo(ua); const target = `${info.deviceVendor} ${info.deviceModel} ${info.userAgent}`; const isOnePlusFamily = /\bOnePlus\b/i.test( `${info.deviceVendor} ${info.userAgent}`, ); if (isOnePlusFamily) { return isOnePlusKeyboardFallbackDevice({ model: info.deviceModel, vendor: info.deviceVendor, }); } return /\b(OPPO|OPlus|ColorOS|CPH\d{3,})\b/i.test(target); } private static getUserAgent(ua: string | null = null): string { if (ua) return ua; if (typeof navigator === "undefined") return ""; return navigator.userAgent; } private static isIPadOS(): boolean { if (typeof navigator === "undefined") return false; return navigator.platform === "MacIntel" && (navigator.maxTouchPoints ?? 0) > 1; } } export function isOnePlusKeyboardFallbackDevice( device: { model: string; vendor: string }, ): boolean { if (!/\bOnePlus\b/i.test(device.vendor)) return false; return ONEPLUS_KEYBOARD_FALLBACK_MODELS.has( device.model.trim().toUpperCase(), ); }