how to hide/show a React component depending on device browser? - javascript

Looking to hide/show a component in a React/Nextjs/tailwind webapp depending on the device that the user is on (e.g. desktop vs tablet vs mobile) since there are certain keys on the keyboard available for desktop but not on table and mobile (e.g. the tab key). Don't want to do it by screen size since this is a device problem (no tab on keyboard) rather than a screen size problem
Initially I thought about getting device type (code referenced from another stackoverflow Q/A), but this seems to fail when the user is on a device such as an ipad and is using the Desktop version for that browser (e.g. Desktop safari and not mobile safari). Is there a better way to handle this such that it can properly check what device the user is on in order to be able to hide/show the react component?
const getDeviceType = () => {
const ua = navigator.userAgent;
if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
return "tablet";
}
if (
/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
ua
)
) {
return "mobile";
}
return "desktop";
};

In JavaScript you can detect browser with JavaScript using window.navigator
Code example according to https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator would be
var sBrowser, sUsrAg =
navigator.userAgent;
// The order matters here, and this may
report false positives for unlisted
browsers.
if (sUsrAg.indexOf("Firefox") > -1) {
sBrowser = "Mozilla Firefox";
// "Mozilla/5.0 (X11; Ubuntu; Linux
x86_64; rv:61.0) Gecko/20100101
Firefox/61.0"
}
alert("You are using: " + sBrowser);
But for react there's another package you can use that is npm install react-device-detect
Code example would be
import { browserName, browserVersion }
from "react-device-detect"
console.log(`${browserName}
${browserVersion}`);
After detecting browser you just conditionally render whatever you want to show

Related

How to detect if device is iOS, Android or Desktop

I need to show different app link depends on user operation system.
Such as IOS, Android or both if user is opening page on Desktop.
I'm using React and Next.js
I tried to use navigator.userAgent with no luck.
It will be perfect to achieve code like below:
import React from "react";
import { AppDownload as AppDownloadProps } from "xxx";
export default function DownloadApp({
appDownload,
}: AppDownloadProps): JSX.Element {
return (
<div>
{isIos && <a href={appDownload?.ios.url}></a>}
{isAndroid && <a href={appDownload?.android.url}></a>}
{isDesktop && <a href={appDownload?.desktop.url}></a>}
</div>
);
}
Possible duplicate of Detect MacOS, iOS, Windows, Android and Linux OS with JS.
You can use window.navigator but will need to parse the result.
You can use navigator.userAgent to detect the useragent in browser and there are lots of other options are available to get agent information.
console.log("agent is:", window?.navigator?.userAgent)
console.log("platform is: ", window?.navigator?.platform)
console.log("user agent data: ", window?.navigator?.userAgentData)

getUserMedia detect front camera

I'm using the 'facingMode' constrain in order to switch between the two cameras,
but I'm not able to fully decided whether the user end has an 'environment' camera (back facing camera).. it's not enough to to count the 'videoinput' of the returned promise of enumerateDevices function.
I tried searching for it and all I found was to use the video MediaTrack label and search for containing "facing back" string, which doesnt seem to be constant in all browsers (IOS for instance).
I'm sure there must be a better way :)
A second answer here. The selection of front (facingMode:'user') and back (facingMode:'environment') cameras is an issue for something I'm working on.
It's possible to tell which camera you're using if you have an open .getUserMedia() stream to a camera. stream.getTracks[0].getCapabilities() gives back an object with either a facingMode:'user' or facingMode:'environment' field in it. But you already must have a stream open to the particular device to get this.
Here's what I have discovered using multiple devices in a mobile-device test farm. These are the order of the device entries yielded by .enumerateDevices().
tl;dr: On iOs devices, the front facing camera is the first videoinput device in the list, and the back-facing camera is the second one. On Android devices, the front facing camera or cameras have the string "front" in their device.label values and the back facing camra or cameras have the string "back".
iOS devices
iOS devices running Mobile Safari: the device.label items are all localized to the currently selected national language.
The devices always appear in this order in the device array, with the front camera always appearing as the first device in the array with device.kind:'videoinput'. Here is the list of devices labels I got from every iOS device I tried, auf Deutsch:
audioinput:iPhone Mikrofon
videoinput:Frontkamera
videoinput:Rückkamera
Some iPhones have multiple camera lenses on the back. Nevertheless, the MediaStream APIs in Mobile Safari only show one camera.
You can tell you’re on an iPhone when
navigator.userAgent.indexOf(' iPhone ') >= 0
You can tell you’re on an iPad when
typeof navigator.maxTouchPoints === 'number'
&& navigator.maxTouchPoints > 2
&& typeof navigator.vendor === 'string'
&& navigator.vendor.indexOf('Apple') >= 0
Notice that iPad Safari now lies and claims it's an Intel Mac. It presents this User-Agent string:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15
Android devices
Android devices: the `device.label' items are not localized.
The devices I looked at had different device.label lists. But the cameras I found all had either the string front or back in the device.label.
A Pixel 3A running Android 11 has this list of devices.
audioinput:Standard
audioinput:Speakerphone
audioinput:Headset earpiece
videoinput:camera2 1, facing front
videoinput:camera2 0, facing back
audiooutput:Standard
A Samsung SM-A205F running Android 9 has these devices in order.
audioinput:Default
audioinput:Speakerphone
audioinput:Headset earpiece
videoinput:camera2 1, facing front
videoinput:camera2 2, facing front
videoinput:camera2 0, facing back
audiooutput:Default
This one enumerates two different front-facing selfiecams. The first one offers higher resolution than the second one.
A Samsung SM-G925I running Android 6.0.1
audioinput:Default
audioinput:Speakerphone
audioinput:Headset earpiece
videoinput:camera2 1, facing front
videoinput:camera2 0, facing back
audiooutput:Default
And, by the way, the results of .getSupportedConstraints() don't help much: both iOS and Android give facingMode:true.
It's my experience, across a wide variety of mobile devices with English as their default language, that the device.label property you get back from enumerateDevices() contains the string 'front' or 'back' for the user or environment camera. But on some devices the string is in upper case. So you need to check for those strings in a case insensitive way.
Also, enumerateDevices() conceals the label values unless your user has granted permission to access media. In most browsers, the permission is sticky. That is, once a user has granted it to your web site, it stays granted. But on Mobile Safari devices the permission is not sticky: your user must grant it each time your page loads. You get your user to grant permission with a getUserMedia() call.
This code should let you know whether you have front and back cameras.
async function hasFrontBack() {
let result = {hasBack: false, hasFront: false, videoDevices: []}
try {
const stream = await navigator.mediaDevices.getUserMedia(
{video: true, audio: false})
let devices = await navigator.mediaDevices.enumerateDevices()
const videoDevices = devices.filter(device => {
if (device.kind === 'videoinput') {
if (device.label && device.label.length > 0) {
if (device.label.toLowerCase().indexOf('back') >= 0) {
result.hasBack = true
} else if (device.label.toLowerCase().indexOf('front') >= 0) {
result.hasFront = true
} else { /* some other device label ... desktop browser? */ }
}
return true
}
return false
})
result.videoDevices = videoDevices
/* drop stream */
const tracks = stream.getTracks()
if (tracks) {
for (let t = 0; t < tracks.length; t++) tracks[t].stop()
}
return result
}
catch (ex) {
/* log and swallow exception, this is a probe only */
console.error(ex)
return result
}
}
Once you have this result, you can use getUserMedia() again without prompting the user for permission again.
To determine if we are using the mobile back camera, use this after the stream has loaded. Currently Firefox does not support getCapabilities(). Tested on MacOS/iOS/Android, Safari/Chrome/Firefox.
if (stream.getTracks()[0].getCapabilities) {
isBackCamera = stream.getTracks()[0].getCapabilities()?.facingMode?.[0] === 'environment';
}
I found a decent solution, still not perfect but can help.
determine by the MediaStreamTrack applied constraints:
MediaStream.getVideoTracks()[0].getConstraints().facingMode
As long as you are not using 'exact' while using 'facingMode', it won't be guaranteed..

Audio Output Device Array is of length 0 on safari

I am working on a video conferencing app that leverages Amazon Chime. I have followed the npm page of Amazon Chime SDK JS and managed to get the server response and initialized the meetingSession. However, the problem is when I try to get an array of audio output devices, it is an array of length zero on Safari whereas in browsers like Chrome and Firefox, it works just fine, and I get an array of non zero length. How do I solve this?
Here is what I have coded so far:
import {
ConsoleLogger,
DefaultDeviceController,
DefaultMeetingSession,
LogLevel,
MeetingSessionConfiguration
} from 'amazon-chime-sdk-js';
const logger = new ConsoleLogger('MyLogger', LogLevel.INFO);
const deviceController = new DefaultDeviceController(logger);
// You need responses from server-side Chime API. See below for details.
const meetingResponse = /* Server response */;
const attendeeResponse = /* Server response */;
const configuration = new MeetingSessionConfiguration(meetingResponse, attendeeResponse);
const meetingSession = new DefaultMeetingSession(
configuration,
logger,
deviceController
);
const audioInputDevices = await meetingSession.audioVideo.listAudioInputDevices();
const audioOutputDevices = await meetingSession.audioVideo.listAudioOutputDevices();
const videoInputDevices = await meetingSession.audioVideo.listVideoInputDevices();
/* Rest of the code... */
When I log the lengths of the above arrays in the console, the length of audioOutputDevices array is zero
in Safari whereas it is non zero in other browsers.
The output/speaker device selection is not enabled by default in Firefox and Safari. So you have to change the default settings:
The below steps are tested on macOS Catalina (v: 10.15.7)
a) Firefox: This issue is discussed here: https://github.com/bigbluebutton/bigbluebutton/issues/12471
In the url bar type about:config
Search in the searchbar for the property media.setsinkid.enabled and set it to true
Relaunch the browser and test (open the following link in the browser https://webrtc.github.io/samples/src/content/devices/input-output/). You should now see values in the drop down of Audio Output Destination
b) Safari (Tested on version: 14.1.2):
Make sure you see the Develop tab in the top right (if not then enable it "Safari > Preferences > Advanced" and then check "Show Develop menu in menu bar" at the bottom)
Navigate to "Develop > Experimental Features > Allow Speaker Device Selection" and make sure "Allow Speaker Device Selection" is checked
Relaunch the browser and test here https://webrtc.github.io/samples/src/content/devices/input-output/
If you want to inform the user then you can have something like below(pseudo code):
ioDevices = await navigator.mediaDevices.enumerateDevices();
let outputDeviceSelectable = false;
for (let device: ioDevices) {
if (device.kind == "audiooutput") {
outputDeviceSelectable = true;
break;
}
}
if (outputDeviceSelectable == false) {
show a pop up to the user to change the default settings
}
This is a known issue according to the Amazon Chime SDK FAQ: Firefox and Safari have known issues disallowing them from listing audio output devices on these browsers. While clients can continue the meeting using the default device, they will not be able to select devices in meetings.

Detect if page is loaded inside WKWebView in JavaScript

How can I reliably detect using javascript that a page is loaded inside a WKWebView? I'd like to be able to detect these scenarios:
iOS & WKWebView
iOS & Safari
not iOS
There is a similar question about UIWebView here. But it's quite old and I'm not sure if same still applies to WKWebView.
The accepted answer doesn't work as tested using the WKWebView vs UIWebView app
As the article mentions, the only HTML5 feature difference is IndexedDB support. So I'd go for a more reliable pattern with:
if (navigator.platform.substr(0,2) === 'iP'){
//iOS (iPhone, iPod or iPad)
var lte9 = /constructor/i.test(window.HTMLElement);
var nav = window.navigator, ua = nav.userAgent, idb = !!window.indexedDB;
if (ua.indexOf('Safari') !== -1 && ua.indexOf('Version') !== -1 && !nav.standalone){
//Safari (WKWebView/Nitro since 6+)
} else if ((!idb && lte9) || !window.statusbar.visible) {
//UIWebView
} else if ((window.webkit && window.webkit.messageHandlers) || !lte9 || idb){
//WKWebView
}
}
You may ask: Why not using the UserAgent? That's because Android browsers use it as settings! So, we should never trust any UAs. Only browser features and property checks as such.
Also I noticed that the QuickTime plugin was always loaded as part of Older Safari and other Browsers in UIWebView. But the plugin is no longer present in WKWebView. So you can use the QuickTime plugin presence as an extra check.
9/23/16 Edit: I adjusted the code for Safari 10 which no longer allowed the sole idb check to be reliable, as mentioned by #xmnboy. To discard Safari 10, it checks for the old webkit engine bug, which only applied until Safari 9.2; and i use a window.statusbar.visible fallback which appears to be a reliable indicator signal after a few comparison tests between iOS 9 and 10. (please check though)
Given the change in behavior to the UIWebView that was introduced by Apple in iOS 10, here's a new answer that combines the original response by #Justin-Michael and the follow-up favorite by #hexalys.
var isWKWebView = false ;
if( navigator.platform.substr(0,2) === 'iP' ) { // iOS detected
if( window.webkit && window.webkit.messageHandlers ) {
isWKWebView = true ;
}
}
It turns out that Justin's answer was really the better feature detection mechanism, because it works for both iOS 9 and iOS 10.
No telling what happens when we get to iOS 11. :-)
Qualification: this test will work if you are using the official Cordova WKWebView plugin to build your webview app, because that plugin does initialize the addScriptMessageHandler method, as noted by #hexalys in the comments to this post. That mechanism is being used by Cordova to define a new JS to native bridge when the WKWebView plugin is present.
Search for addScriptMessageHandler in that plugin repo and see the very end of the ios-wkwebview-exec.js file in that repo for some implementation details (or search for the string window.webkit.messageHandlers in that file).
In iOS, you could add this code to establish communication between javascript and objective-c:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:self name:#"javascript_observer"];
configuration.userContentController = controller;
...
webview = [[WKWebView alloc] initWithFrame:... configuration: configuration];
In javascript, you could test the connection like this:
if ( window.webkit != undefined ){
//javascript is running in webview
}
It seems that because the latest iOS Chrome uses WKWebView as a rendering engine, Chrome is detected as WKWebView.
ua.indexOf('CriOS') !== -1
will helps to distinguish Chrome from WKWebView in App.
You can check for the existence of window.webkit.messageHandlers which WKWebKit uses to receive messages from JavaScript. If it exists, you're inside a WKWebView.
That combined with a simple user agent check should do the trick:
var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);
var isWKWebView = false;
if (window.webkit && window.webkit.messageHandlers) {
isWKWebView = true;
}

What's the best way to detect a webOS tablet with jQuery / plain JS

I'm looking for the best way to detect a webOS tablet using plain JS and if it's any easier also using jQuery. The user agent of the tablet should look something like this:
User-Agent:Mozilla/5.0 (webOS/1.3; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Desktop/1.0
So an easy way would be:
var deviceAgent = navigator.userAgent.toLowerCase();
webOS = deviceAgent.match(/(webos)/);
Is that the best way to do it already? You're likely going to say detect the feature you need to make certain is present but that won't work for me because the feature I want is present but not working as it would on any desktop, so I really just want to know is this a webOS device or not.
Update: Just found that the tablet really uses another user agent:
Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; xx-xx) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.48 Safari/534.6 TouchPad/1.0
So the above should probably rather be:
var deviceAgent = navigator.userAgent.toLowerCase();
webOS = deviceAgent.match(/(webos|hpwos)/);
Here's a function in PHP that will detect WebOS and any other mobile device you could need. Less than 1kb in code =)
function detectMobileDevice() {
if(preg_match('/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i', $_SERVER['HTTP_USER_AGENT'])) {
return true;
}
else {
return false;
}
}
if you want to do ONLY webOS, change line 2 to:
if(preg_match('/(webos)/i', $_SERVER['HTTP_USER_AGENT'])) {
to use:
if(detectMobileDevice()) {
// If mobile device detected, do something
}
else {
// Otherwise, do something else...
}
if you need more details, visit here: http://www.justindocanto.com/scripts/detect-a-mobile-device-in-php-using-detectmobiledevice
I don't know if you can do any feature detection that'll only identify WebOS. It's WebKit-based, so all other WebKit-based platforms will have the same features. Looking at Zepto.js' source, they're doing exactly the same as you are:
ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/)
(The 2nd capture is the version)
From detect.js

Categories

Resources