|
1 | 1 | interface BrowserConfig { |
2 | 2 | name: string; |
3 | 3 | pattern: RegExp; |
4 | | - versionPattern?: RegExp; |
| 4 | + versionPattern: RegExp; |
5 | 5 | } |
6 | 6 |
|
7 | 7 | interface IBrowserResult { |
8 | 8 | name: string; |
9 | 9 | version: string; |
10 | 10 | } |
11 | 11 |
|
12 | | -const PATTERNS = { |
13 | | - LIKE_ANDROID: /like android/i, |
14 | | - ANDROID: /android/i, |
15 | | - IOS_DEVICES: /(iphone|ipod|ipad)/i, |
16 | | - TABLET: /tablet/i, |
17 | | - TABLET_PC: /tablet pc/i, |
18 | | - MOBILE: /[^-]mobi/i, |
19 | | - NEXUS_MOBILE: /nexus\s*[0-6]\s*/i, |
20 | | - NEXUS_TABLET: /nexus\s*[0-9]+/i, |
21 | | - GENERIC_VERSION: /version\/(\d+(?:\.\d+)?)/i, |
22 | | -} as const; |
23 | | - |
24 | 12 | const BROWSER_CONFIGS: BrowserConfig[] = [ |
25 | 13 | { |
26 | 14 | name: 'Opera', |
27 | 15 | pattern: /(?:opera|opr|opios)/i, |
28 | | - versionPattern: /(?:opera|opr|opios)[\s\/](\d+(?:\.\d+)?)/i, |
| 16 | + versionPattern: /(?:opera|opr|opios)[ /](\d+(?:\.\d+)?)/i, |
29 | 17 | }, |
30 | 18 | { |
31 | 19 | name: 'Facebook', |
32 | 20 | pattern: /FBAN\//i, |
33 | 21 | versionPattern: /FBAV\/(\d+(?:\.\d+)?)/i, |
34 | 22 | }, |
35 | 23 | { |
36 | | - name: 'Samsung Internet for Android', |
| 24 | + name: 'Samsung Browser', |
37 | 25 | pattern: /samsungbrowser/i, |
38 | | - versionPattern: /samsungbrowser[\s\/](\d+(?:\.\d+)?)/i, |
| 26 | + versionPattern: /samsungbrowser[ /](\d+(?:\.\d+)?)/i, |
39 | 27 | }, |
40 | 28 | { |
41 | 29 | name: 'Yandex Browser', |
42 | 30 | pattern: /yabrowser/i, |
43 | | - versionPattern: /yabrowser[\s\/](\d+(?:\.\d+)?)/i, |
| 31 | + versionPattern: /yabrowser[ /](\d+(?:\.\d+)?)/i, |
44 | 32 | }, |
45 | 33 | { |
46 | 34 | name: 'Vivaldi', |
47 | 35 | pattern: /vivaldi/i, |
48 | | - versionPattern: /vivaldi[\s\/](\d+(?:\.\d+)?)/i, |
| 36 | + versionPattern: /vivaldi[ /](\d+(?:\.\d+)?)/i, |
49 | 37 | }, |
50 | 38 | { |
51 | 39 | name: 'UC Browser', |
52 | 40 | pattern: /ucbrowser/i, |
53 | | - versionPattern: /ucbrowser[\s\/](\d+(?:\.\d+)?)/i, |
| 41 | + versionPattern: /ucbrowser[ /](\d+(?:\.\d+)?)/i, |
54 | 42 | }, |
55 | 43 | { |
56 | 44 | name: 'Microsoft Edge', |
57 | | - pattern: /(?:edge|edgios|edga|edg)/i, |
58 | | - versionPattern: /(?:edge|edgios|edga|edg)[\s\/](\d+(?:\.\d+)?)/i, |
| 45 | + pattern: /edg/i, |
| 46 | + versionPattern: /edg[ /](\d+(?:\.\d+)?)/i, |
59 | 47 | }, |
60 | 48 | { |
61 | 49 | name: 'Firefox', |
62 | 50 | pattern: /firefox|iceweasel|fxios/i, |
63 | | - versionPattern: /(?:firefox|iceweasel|fxios)[\s\/](\d+(?:\.\d+)?)/i, |
| 51 | + versionPattern: /(?:firefox|iceweasel|fxios)[ /](\d+(?:\.\d+)?)/i, |
64 | 52 | }, |
65 | 53 | { |
66 | 54 | name: 'Chromium', |
67 | 55 | pattern: /chromium/i, |
68 | | - versionPattern: /chromium[\s\/](\d+(?:\.\d+)?)/i, |
| 56 | + versionPattern: /chromium[ /](\d+(?:\.\d+)?)/i, |
69 | 57 | }, |
70 | 58 | { |
71 | 59 | name: 'Chrome', |
72 | 60 | pattern: /chrome|crios|crmo/i, |
73 | | - versionPattern: /(?:chrome|crios|crmo)[\s\/](\d+(?:\.\d+)?)/i, |
| 61 | + versionPattern: /(?:chrome|crios|crmo)[ /](\d+(?:\.\d+)?)/i, |
74 | 62 | }, |
75 | 63 | { |
76 | 64 | name: 'Safari', |
77 | 65 | pattern: /safari|applewebkit/i, |
78 | | - versionPattern: /version[\s\/](\d+(?:\.\d+)?)/i, |
| 66 | + versionPattern: /version[ /](\d+(?:\.\d+)?)/i, |
79 | 67 | }, |
80 | 68 | ]; |
81 | 69 |
|
82 | | -const matchUserAgent = ( |
83 | | - userAgent: string, |
84 | | - position: number, |
85 | | - pattern: RegExp, |
86 | | -): string => userAgent.match(pattern)?.[position] || ''; |
87 | | - |
88 | | -function extractVersion(userAgent: string, config: BrowserConfig): string { |
89 | | - if (!config.versionPattern) |
90 | | - return matchUserAgent(userAgent, 1, PATTERNS.GENERIC_VERSION); |
91 | | - const match = userAgent.match(config.versionPattern); |
92 | | - return match?.[1] || ''; |
93 | | -} |
94 | | - |
95 | 70 | export function getBrowser(userAgent: string): IBrowserResult { |
96 | 71 | for (const config of BROWSER_CONFIGS) { |
97 | 72 | if (config.pattern.test(userAgent)) { |
98 | | - return { name: config.name, version: extractVersion(userAgent, config) }; |
| 73 | + const version = userAgent.match(config.versionPattern)?.[1] ?? ''; |
| 74 | + return { name: config.name, version }; |
99 | 75 | } |
100 | 76 | } |
101 | | - return { |
102 | | - name: matchUserAgent(userAgent, 1, /^(.*?)[\s\/]/) || 'Unknown', |
103 | | - version: matchUserAgent(userAgent, 2, /^(.*?)[\s\/](.+?)[\s]/) || '', |
104 | | - }; |
| 77 | + return { name: 'Unknown', version: '' }; |
105 | 78 | } |
| 79 | +const isAndroid = (userAgent: string): boolean => |
| 80 | + !/like android/i.test(userAgent) && /android/i.test(userAgent); |
106 | 81 |
|
107 | | -const isAndroidDevice = (userAgent: string): boolean => |
108 | | - !PATTERNS.LIKE_ANDROID.test(userAgent) && PATTERNS.ANDROID.test(userAgent); |
| 82 | +const getIOSDeviceType = (userAgent: string): string => |
| 83 | + userAgent.match(/(iphone|ipod|ipad)/i)?.[1]?.toLowerCase() || ''; |
109 | 84 |
|
110 | | -export function getIOSDeviceType(userAgent: string): string { |
111 | | - let deviceType = matchUserAgent( |
112 | | - userAgent, |
113 | | - 1, |
114 | | - PATTERNS.IOS_DEVICES, |
115 | | - ).toLowerCase(); |
| 85 | +export function isTabletBrowser(userAgent = navigator.userAgent): boolean { |
| 86 | + const ios = getIOSDeviceType(userAgent); |
116 | 87 |
|
117 | | - if ( |
118 | | - !deviceType && |
119 | | - navigator.platform === 'MacIntel' && |
120 | | - navigator.maxTouchPoints > 2 && |
121 | | - !(window as { MSStream?: unknown })?.MSStream |
122 | | - ) { |
123 | | - deviceType = 'ipad'; |
124 | | - } |
125 | | - return deviceType; |
126 | | -} |
127 | | - |
128 | | -export function isTablet(userAgent: string): boolean { |
129 | | - const isAndroid = isAndroidDevice(userAgent); |
130 | | - const iOSDevice = getIOSDeviceType(userAgent); |
131 | 88 | return ( |
132 | | - (PATTERNS.TABLET.test(userAgent) && !PATTERNS.TABLET_PC.test(userAgent)) || |
133 | | - iOSDevice === 'ipad' || |
134 | | - (isAndroid && !PATTERNS.MOBILE.test(userAgent)) || |
135 | | - (!PATTERNS.NEXUS_MOBILE.test(userAgent) && |
136 | | - PATTERNS.NEXUS_TABLET.test(userAgent)) |
| 89 | + (/tablet/i.test(userAgent) && !/tablet pc/i.test(userAgent)) || |
| 90 | + ios === 'ipad' || |
| 91 | + (isAndroid(userAgent) && !/[^-]mobi/i.test(userAgent)) || |
| 92 | + (!/nexus\s*[0-6]\s*/i.test(userAgent) && /nexus\s*\d+/i.test(userAgent)) |
137 | 93 | ); |
138 | 94 | } |
139 | 95 |
|
140 | | -export function isMobile(userAgent: string): boolean { |
141 | | - const isTabletDevice = isTablet(userAgent); |
142 | | - const isAndroid = isAndroidDevice(userAgent); |
143 | | - const iOSDevice = getIOSDeviceType(userAgent); |
| 96 | +export function isMobileBrowser(userAgent = navigator.userAgent): boolean { |
| 97 | + if (isTabletBrowser(userAgent)) return false; |
| 98 | + |
| 99 | + const ios = getIOSDeviceType(userAgent); |
144 | 100 | return ( |
145 | | - !isTabletDevice && |
146 | | - (PATTERNS.MOBILE.test(userAgent) || |
147 | | - iOSDevice === 'iphone' || |
148 | | - iOSDevice === 'ipod' || |
149 | | - isAndroid || |
150 | | - PATTERNS.NEXUS_MOBILE.test(userAgent)) |
| 101 | + /[^-]mobi/i.test(userAgent) || |
| 102 | + ios === 'iphone' || |
| 103 | + ios === 'ipod' || |
| 104 | + isAndroid(userAgent) |
151 | 105 | ); |
152 | 106 | } |
0 commit comments