UNPKG

8.26 kBJavaScriptView Raw
1// This method should be used instead of window.navigator.userAgent, which
2// is not defined in React Native and results in an error.
3// (Actually, if it *is* defined in React Native, it's not meant for us, but
4// for our customer's code; that's why we don't just simply override it globally).
5export function getUserAgent() {
6 if (
7 !isReactNative() &&
8 typeof window !== 'undefined' &&
9 window.navigator &&
10 window.navigator.userAgent
11 ) {
12 return window.navigator.userAgent;
13 }
14 return '';
15}
16
17export function isReactNative() {
18 return (
19 typeof navigator !== 'undefined' &&
20 navigator.product &&
21 navigator.product === 'ReactNative'
22 );
23}
24
25export function isIOS() {
26 const userAgent = getUserAgent();
27 return !!userAgent.match(/iPad|iPhone|iPod/i);
28}
29
30// Only valid if cam/mic are accessible from browser
31export function isUserMediaAccessible() {
32 return (
33 navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia
34 );
35}
36
37// Returns whether we should allow screen sharing from this browser.
38//
39// Note: technically we *could* try to support screen sharing from any browser where
40// isDisplayMediaAccessible() is true (PeerToPeer.js is mostly set up to do so).
41// However, limiting screen sharing to only those that support the Unified Plan SDP
42// format lets us simplify code paths on the receiving end of screen shares: in order
43// to check whether to always expect a single inbound video track, we simply have to
44// check whether we're a browser that only supports the older Plan B SDP format (see below).
45// Additionally, limiting screen sharing this way reduces our test matrix.
46export function isScreenSharingSupported() {
47 return isDisplayMediaAccessible() && canUnifiedPlan();
48}
49
50const supportedBrowsersForVideoProcessors = ['Chrome', 'Firefox'];
51
52export function isVideoProcessingSupported() {
53 if (isReactNative()) return false;
54 if (browserMobile_p()) return false;
55 return supportedBrowsersForVideoProcessors.includes(getBrowserName());
56}
57
58export function isSfuSupported() {
59 if (isReactNative()) return true;
60 return browserVideoSupported_p();
61}
62
63export function canUnifiedPlan() {
64 return browserCanUnifiedPlan(getBrowserName(), getBrowserVersion());
65}
66
67export function browserCanUnifiedPlan(browserName, browserVersion) {
68 if (!(browserName && browserVersion)) {
69 return false;
70 }
71 switch (browserName) {
72 case 'Chrome':
73 return browserVersion.major >= 75;
74 case 'Safari':
75 // This is the check that Apple suggests in https://webkit.org/blog/8672/on-the-road-to-webrtc-1-0-including-vp8/,
76 // plus a workaround that was already in place here for a Safari 13.0.0 bug, forcing it into Plan B.
77 return (
78 RTCRtpTransceiver.prototype.hasOwnProperty('currentDirection') &&
79 !(
80 browserVersion.major === 13 &&
81 browserVersion.minor === 0 &&
82 browserVersion.point === 0
83 )
84 );
85 // Note: We now only support Firefox 80+ so this should always be true
86 case 'Firefox':
87 return browserVersion.major >= 67;
88 }
89 return false;
90}
91
92export function browserVideoSupported_p() {
93 return isUserMediaAccessible() && !browserNeedsUpgrade();
94}
95
96export function isAndroidApp() {
97 return getUserAgent().match(/DailyAnd\//);
98}
99
100export function isAndroidWeb() {
101 return getUserAgent().match(/Linux; Android/);
102}
103
104export function browserMobile_p() {
105 const userAgent = getUserAgent();
106 if (userAgent.match(/Mobi/) || userAgent.match(/Android/)) {
107 return true;
108 }
109 if (isAndroidApp()) {
110 return true;
111 }
112}
113
114export function browserNeedsUpgrade() {
115 let browser = getBrowserName(),
116 ua = getUserAgent(),
117 version;
118 if (!ua) {
119 return true;
120 }
121 switch (browser) {
122 case 'Chrome':
123 // Includes Chromium-based browsers
124 version = getChromeVersion();
125 return version.major && version.major > 0 && version.major < 61;
126 case 'Firefox':
127 version = getFirefoxVersion();
128 return version.major < 78;
129 case 'Safari':
130 version = getSafariVersion();
131 return version.major < 12;
132 default:
133 return true;
134 }
135}
136
137export function getBrowserName() {
138 if (typeof window !== 'undefined') {
139 const userAgent = getUserAgent();
140 // Treat supported WKWebView as Safari. Check for this first just in case
141 // 3rd-party browsers on iOS decide to customize their user agent strings to
142 // match the other conditions.
143 if (isSupportedIOSEnvironment()) {
144 return 'Safari';
145 } else if (userAgent.indexOf('Edge') > -1) {
146 // Note: check will (purposefully) fail for chromium-based Edge
147 // since the user-agent for chromium-based Edge reports `Edg`
148 // (or EdgA (android) or EdgiOS)
149 // Also note: getBrowserName is primarily used for internal
150 // logic, so this should go away eventually. However, it is used
151 // in the old prebuilt UI for some upgrade messaging so leaving
152 // it in until Edge or the old prebuilt is really no longer a thing
153 return 'Edge';
154
155 // } else if (userAgent.indexOf('OPR') > -1 ||
156 // userAgent.indexOf('Opera') > -1) {
157 // return 'Opera';
158 } else if (userAgent.match(/Chrome\//)) {
159 // Includes Chromium-based browsers
160 return 'Chrome';
161 } else if (userAgent.indexOf('Safari') > -1) {
162 return 'Safari';
163 } else if (userAgent.indexOf('Firefox') > -1) {
164 return 'Firefox';
165 } else if (
166 userAgent.indexOf('MSIE') > -1 ||
167 userAgent.indexOf('.NET') > -1
168 ) {
169 return 'IE';
170 } else {
171 return 'Unknown Browser';
172 }
173 }
174}
175
176export function getBrowserVersion() {
177 let name = getBrowserName();
178 switch (name) {
179 case 'Chrome':
180 // Includes Chromium-based browsers
181 return getChromeVersion();
182 case 'Safari':
183 return getSafariVersion();
184 case 'Firefox':
185 return getFirefoxVersion();
186 case 'Edge':
187 return getEdgeVersion();
188 }
189}
190
191export function getChromeVersion() {
192 let major = 0,
193 minor = 0,
194 build = 0,
195 patch = 0,
196 opera = false;
197 if (typeof window !== 'undefined') {
198 const userAgent = getUserAgent(),
199 match = userAgent.match(/Chrome\/(\d+).(\d+).(\d+).(\d+)/);
200 if (match) {
201 try {
202 major = parseInt(match[1]);
203 minor = parseInt(match[2]);
204 build = parseInt(match[3]);
205 patch = parseInt(match[4]);
206 opera = userAgent.indexOf('OPR/') > -1;
207 } catch (e) {}
208 }
209 }
210 return { major, minor, build, patch, opera };
211}
212
213// Mobile Safari or WKWebView on iOS/iPadOS >= 14.3
214export function isSupportedIOSEnvironment() {
215 return isIOS() && isUserMediaAccessible();
216}
217
218function isDisplayMediaAccessible() {
219 return !!(
220 navigator &&
221 navigator.mediaDevices &&
222 navigator.mediaDevices.getDisplayMedia
223 );
224}
225
226function getSafariVersion() {
227 let major = 0,
228 minor = 0,
229 point = 0;
230 if (typeof window !== 'undefined') {
231 const userAgent = getUserAgent(),
232 match = userAgent.match(/Version\/(\d+).(\d+)(.(\d+))?/);
233 if (match) {
234 try {
235 major = parseInt(match[1]);
236 minor = parseInt(match[2]);
237 point = parseInt(match[4]);
238 } catch (e) {}
239 } else if (isSupportedIOSEnvironment()) {
240 // Hack: treat supported WKWebView like Safari 14.0.3 (no need to be
241 // precise; just needs to be new enough to appear supported, and this was
242 // the Safari version around the time WKWebView WebRTC support was added)
243 major = 14;
244 minor = 0;
245 point = 3;
246 }
247 }
248 return { major, minor, point };
249}
250
251function getFirefoxVersion() {
252 let major = 0,
253 minor = 0;
254 if (typeof window !== 'undefined') {
255 const userAgent = getUserAgent(),
256 match = userAgent.match(/Firefox\/(\d+).(\d+)/);
257 if (match) {
258 try {
259 major = parseInt(match[1]);
260 minor = parseInt(match[2]);
261 } catch (e) {}
262 }
263 }
264 return { major, minor };
265}
266
267function getEdgeVersion() {
268 let major = 0,
269 minor = 0;
270 if (typeof window !== 'undefined') {
271 const userAgent = getUserAgent(),
272 match = userAgent.match(/Edge\/(\d+).(\d+)/);
273 if (match) {
274 try {
275 major = parseInt(match[1]);
276 minor = parseInt(match[2]);
277 } catch (e) {}
278 }
279 }
280 return { major, minor };
281}