| 1 | export type ClientDeviceType = 'mobile' | 'desktop' | 'tablet' | 'unknown' |
| 2 | |
| 3 | export interface ClientDeviceInfo { |
| 4 | deviceType: ClientDeviceType |
| 5 | os?: string |
| 6 | browser?: string |
| 7 | } |
| 8 | |
| 9 | interface NavigatorUserAgentData { |
| 10 | mobile?: boolean |
| 11 | platform?: string |
| 12 | } |
| 13 | |
| 14 | type NavigatorWithUserAgentData = Navigator & { |
| 15 | userAgentData?: NavigatorUserAgentData |
| 16 | } |
| 17 | |
| 18 | function getRawBrowserUserAgent() { |
| 19 | if (typeof window === 'undefined') |
| 20 | return '' |
| 21 | |
| 22 | return window.navigator.userAgent || '' |
| 23 | } |
| 24 | |
| 25 | function getNavigatorWithUserAgentData(): NavigatorWithUserAgentData | undefined { |
| 26 | if (typeof window === 'undefined') |
| 27 | return undefined |
| 28 | |
| 29 | return window.navigator as NavigatorWithUserAgentData |
| 30 | } |
| 31 | |
| 32 | function isIpadOsDesktopMode(userAgent: string, platform: string, maxTouchPoints: number) { |
| 33 | return /macintosh|mac os x/i.test(userAgent) && /mac/i.test(platform) && maxTouchPoints > 1 |
| 34 | } |
| 35 | |
| 36 | function detectClientDeviceType(userAgent: string, navigatorInfo: NavigatorWithUserAgentData): ClientDeviceType { |
| 37 | const platform = navigatorInfo.userAgentData?.platform || navigatorInfo.platform || '' |
| 38 | const maxTouchPoints = navigatorInfo.maxTouchPoints || 0 |
| 39 | const mobileFromUserAgentData = navigatorInfo.userAgentData?.mobile |
| 40 | const hasCoarsePointer = typeof window !== 'undefined' && typeof window.matchMedia === 'function' |
| 41 | ? window.matchMedia('(pointer: coarse)').matches |
| 42 | : false |
| 43 | const screenWidth = typeof window !== 'undefined' ? window.screen.width || window.innerWidth : 0 |
| 44 | const screenHeight = typeof window !== 'undefined' ? window.screen.height || window.innerHeight : 0 |
| 45 | const minScreenSize = Math.min(screenWidth, screenHeight) |
| 46 | |
| 47 | if (isIpadOsDesktopMode(userAgent, platform, maxTouchPoints)) |
| 48 | return 'tablet' |
| 49 | |
| 50 | if (/ipad|tablet|kindle|silk|playbook|nexus 7|nexus 9|sm-t|tab/i.test(userAgent)) |
| 51 | return 'tablet' |
| 52 | |
| 53 | if (/android/i.test(userAgent) && !/mobile/i.test(userAgent)) |
| 54 | return 'tablet' |
| 55 | |
| 56 | if (mobileFromUserAgentData === true) |
| 57 | return 'mobile' |
| 58 | |
| 59 | if (/mobi|iphone|ipod|blackberry|iemobile|opera mini|phone/i.test(userAgent)) |
| 60 | return 'mobile' |
| 61 | |
| 62 | if (mobileFromUserAgentData === false) |
| 63 | return 'desktop' |
| 64 | |
| 65 | if (/windows nt|macintosh|x11|linux x86_64|cros/i.test(userAgent)) |
| 66 | return 'desktop' |
| 67 | |
| 68 | if (hasCoarsePointer && minScreenSize > 0 && minScreenSize < 768) |
| 69 | return 'mobile' |
| 70 | |
| 71 | return 'unknown' |
| 72 | } |
| 73 | |
| 74 | function detectClientOs(userAgent: string, navigatorInfo: NavigatorWithUserAgentData) { |
| 75 | const platform = navigatorInfo.userAgentData?.platform || navigatorInfo.platform || '' |
| 76 | const maxTouchPoints = navigatorInfo.maxTouchPoints || 0 |
| 77 | |
| 78 | if (isIpadOsDesktopMode(userAgent, platform, maxTouchPoints)) |
| 79 | return 'iPadOS' |
| 80 | |
| 81 | if (/iphone|ipad|ipod/i.test(userAgent)) |
| 82 | return 'iOS' |
| 83 | |
| 84 | if (/android/i.test(userAgent)) |
| 85 | return 'Android' |
| 86 | |
| 87 | if (/windows nt/i.test(userAgent)) |
| 88 | return 'Windows' |
| 89 | |
| 90 | if (/mac os x|macintosh/i.test(userAgent)) |
| 91 | return 'macOS' |
| 92 | |
| 93 | if (/cros/i.test(userAgent)) |
| 94 | return 'Chrome OS' |
| 95 | |
| 96 | if (/linux/i.test(userAgent)) |
| 97 | return 'Linux' |
| 98 | |
| 99 | return undefined |
| 100 | } |
| 101 | |
| 102 | function detectClientBrowser(userAgent: string) { |
| 103 | if (/edgios|edga|edg\//i.test(userAgent)) |
| 104 | return 'Edge' |
| 105 | |
| 106 | if (/opr\//i.test(userAgent) || /opera/i.test(userAgent)) |
| 107 | return 'Opera' |
| 108 | |
| 109 | if (/crios/i.test(userAgent)) |
| 110 | return 'Chrome' |
| 111 | |
| 112 | if (/fxios/i.test(userAgent)) |
| 113 | return 'Firefox' |
| 114 | |
| 115 | if (/firefox\//i.test(userAgent)) |
| 116 | return 'Firefox' |
| 117 | |
| 118 | if (/samsungbrowser/i.test(userAgent)) |
| 119 | return 'Samsung Browser' |
| 120 | |
| 121 | if (/chrome\//i.test(userAgent) || /chromium/i.test(userAgent)) |
| 122 | return 'Chrome' |
| 123 | |
| 124 | if (/safari\//i.test(userAgent)) |
| 125 | return 'Safari' |
| 126 | |
| 127 | if (/msie|trident/i.test(userAgent)) |
| 128 | return 'Internet Explorer' |
| 129 | |
| 130 | return undefined |
| 131 | } |
| 132 | |
| 133 | export function getClientDeviceInfo(): ClientDeviceInfo { |
| 134 | const navigatorInfo = getNavigatorWithUserAgentData() |
| 135 | if (!navigatorInfo) { |
| 136 | return { deviceType: 'unknown' } |
| 137 | } |
| 138 | |
| 139 | const userAgent = getRawBrowserUserAgent() |
| 140 | |
| 141 | return { |
| 142 | deviceType: detectClientDeviceType(userAgent, navigatorInfo), |
| 143 | os: detectClientOs(userAgent, navigatorInfo), |
| 144 | browser: detectClientBrowser(userAgent), |
| 145 | } |
| 146 | } |
| 147 |