Add to Home screen (A2HS) feature detection (no user agent sniffing) - javascript

I have a PWA where Android users are prompted to Add the PWA to their home screen, but I would also like to show a tooltip for Safari users on iOS showing how to add the PWA to their Home screen since it's not a supported feature for them.
I was thinking about using feature detection but I can't find a way to do it, something like:
if ("a2hs" in navigator) {
// Do something for browsers supporting A2HS feature
} else {
// Show the tooltip if the browser does not support A2HS
}
I also thought I could use something like this:
const isIos = () => {
return window.navigator.vendor === 'Apple Computer, Inc.';
};
but this is also returning true for users of Chrome on iOS :(
Any idea how I can detect if a user is using Safari on iOS?
Note: I also want to avoid using user agent sniffing. I think I read almost all topics related to this subject on SO but so far I couldn't find any solution not using user agent sniffing.

a partial solution:
if('BeforeInstallPromptEvent' in window)
alert('Chrome-style PWA install experience supported!');
if('standalone' in navigator) // only available on iOS - but also on alternative browsers on iOS without Add-to-Homescreen support
alert('iOS Safari-style PWA Add-to-Homescreen maybe supported!');
Open problems:
avoid false positives on iOS - if you could accept using the user agent for that, you could use navigator.userAgent.match(/(iOS|OPT|Brave|GSA|DuckDuckGo|Snapchat)\//) to detect most alternative browsers; For explanation: iOS in the regex should detect CriOS|FxiOS|EdgiOS|OPiOS (Chrome/Firefox/Edge/Opera mini); Does anbody have a way to detect alternative browsers on iOS without using the user agent?
it does not detect Firefox on Android; and I don't know if other browsers based on Firefox for Android support PWAs (e.g. QuantMobile, PrivacyWall, Fennec, KAIOS - all tokens that appear together with Firefox and Android in the user agent string)
Addition (untested & unreliable):
I did some more research:
A sure sign that you are in a webview on iOS is (or was?) that navigator.mediaDevices.getUserMedia or maybe navigator.mediaDevices is undefined. But I also read that Apple implemented it some months ago - so there will be webviews for which this isn't true.
Another hint might that if navigator.doNotTrack is undefined, it could mean that you are in a webview (or that Apple decided to remove this deprecated property from Safari...)

Related

Not possible to detect webview with Javascript and user agent on old android versions

I'm struggling on solving a really annoying issue for android.
We are running our PWA inside of a webview - that's how websites can be ported to android apps.
I'm trying to solve the issue with detecting if it's running inside of a webview on android - if it is it shouldn't display "use app" button and cookie bar etc.
The old android versions just refuses to detect this - hence still showing the button. Only the new ones detect it, android 11 etc.
Have used the following NPM package that is supposed to detect it but still to no success.
https://www.npmjs.com/package/is-ua-webview
I faced the same problem. This package is-ua-webview is quite old (the last update was about 2 years ago) and I suggest it doesn't work in the right way.
The current problem is here: https://github.com/atomantic/is-ua-webview/blob/master/data/rules.js#L8
I don't know why, but the author tries to detect WebView by 0.0.0 version.
'Android.*(wv|.0.0.0)',
But as you can see in many sources there are combinations with devices and browsers that have Android.*.0.0.0 string in the User-Agent, but it's still not WebView, it's a mobile browser.
For example, this is the User-Agent string in Mobile Chrome browser on Samsung Galaxy S21 (Android 12) Mozilla/5.0 (Linux; Android 12; SM-G996B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Mobile Safari/537.36. It contains Android.*102.0.0.0 and this package defines this browser as a WebView.
Actually, I can't find how the .0.0.0 ending binds with the WebView (maybe just this: https://developer.chrome.com/docs/multidevice/webview/#what-version-of-chrome-is-it-based-on, but it's an article of 2014 year), but I found this helpful article about detecting WebView: Developer Chrome - webview user agent
We need to check Version/_X.X_ (Version/4.0 for example) in our User-Agent string. Even in this article was said:
Don't rely on the specific Chrome version number (for example, 30.0.0.0) as the version numbers changes with each release
Actually whole code of is-ua-webview package looks like this:
const rules = [
// if it says it's a webview, let's go with that
'WebView',
// iOS webview will be the same as safari but missing "Safari"
'(iPhone|iPod|iPad)(?!.*Safari)',
// Android Lollipop and Above: webview will be the same as native but it will contain "wv"
// Android KitKat to lollipop webview will put {version}.0.0.0
'Android.*(wv|.0.0.0)',
// old chrome android webview agent
'Linux; U; Android'
]
var webviewRegExp = new RegExp('(' + rules.join('|') + ')', 'ig')
module.exports = function isWebview(ua) {
return !!ua.match(webviewRegExp)
}
We can just replace the 'Android.*(wv|.0.0.0)', string to new 'Version/[0-9]\.[0-9]' according to this article Developer Chrome - webview user agent
Final solution:
const rules = [
// if it says it's a webview, let's go with that
'WebView',
// iOS webview will be the same as safari but missing "Safari"
'(iPhone|iPod|iPad)(?!.*Safari)',
// https://developer.chrome.com/docs/multidevice/user-agent/#webview_user_agent
'Android.*Version/[0-9]\.[0-9]',
// Also, we should save the wv detected for Lollipop
// Android Lollipop and Above: webview will be the same as native but it will contain "wv"
'Android.*wv',
// old chrome android webview agent
'Linux; U; Android'
]
var webviewRegExp = new RegExp('(' + rules.join('|') + ')', 'ig')
module.exports = function isWebview(ua) {
return !!ua.match(webviewRegExp)
}
By the way, as I understood sometimes the user can see the whole functionality of the Chrome browser in WebView: https://developer.android.com/about/versions/nougat/android-7.0#webview
I hope it helps you. Thanks
you can explicitly set the agent string of the webview in your app to a unique string, and detect that agent string on your web app side.
webview.getSettings().setUserAgentString("my-webview-agent-string");

Detect edge://flags/#edge-click-once enabled in Chromium Edge

We have an application that starts via Click Once in the browser. Since Chromium Edge, Click Once isn't enabled by default. You can enable this setting via edge://flags/#edge-click-once.
I would like to detect if this setting is enabled. If not i would like to direct them to a support page with instructions to enable it.
I don't have access to clients browser in anyway. So I would like to detect this via JavaScript.
I think the flags can't be accessed through js, because js codes can be executed in any browsers. We can't only target the features in a certain browser. I also searched a lot and find no way. Maybe Extension can do this, but I think that will take a lot of effort to research.
As this feature is not enabled by Edge Chromium by default, I think the easiest workaround is to detect the browser version, if Edge Chromium, then remind the user to check if the feature is enabled:
var checkbrowser = (function(agent) {
if (agent.indexOf("edg") > -1) { //check if Edge Chromium
return "the page of showing how to enable clickonce support";
} else {
return "not Edge Chromium"
}
})(window.navigator.userAgent.toLowerCase());
document.body.innerHTML = checkbrowser;
Besides, if your application is used in an enterprise and someone has access to the group policy, you can also suggest IT department to set this group policy: ClickOnceEnabled to enable clickonce in Edge Chromium.

How to access camera on iOS11 home screen web app?

Summary
We cannot access camera from an iOS11 (public release) home screen web app using either WebRTC or the file input, details below. How can our users continue to access the camera please?
We are serving the web app page over https.
Update, April
The public release of iOS 11.3 seems to have fixed the issue and file input camera access is working again!
Update, March
As people here have said the Apple docs advise web app camera function is returning in 11.3 along with service workers. This is good but we are not sure yet if we want to everyone to to reinstall again until we can thoroughly test on 11.3GM.
Solution, November
We lost hope Apple want to fix this and moved forward. Modified our web app to remove the iOS "Add to home screen" function and asked affected users to remove any previous home screen icon.
Update, 6 December
iOS 11.2 and iOS 11.1.2 don't fix.
Workarounds, 21 September
Seems we could ask existing customers of the web app
not upgrade to iOS11 - good luck with that :)
take photos in iOS camera and then select them back in the web app
wait for next ios beta
reinstall as a Safari in-browser page (after we remove ATHS logic)
switch to Android
File Input
Our current production code uses a file input which has worked fine for years with iOS 10 and older. On iOS11 it works as a Safari tab but not from the home screen app. In the latter case the camera is opened and only a black screen is shown, hence it is unusable.
<meta name="apple-mobile-web-app-capable" content="yes">
...
<input type="file" accept="image/*">
WebRTC
Safari 11 on iOS11 offers WebRTC media capture which is great.
We can capture a camera image to canvas on a normal web page on desktop and mobile using navigator.mediaDevices.getUserMedia per the sample code linked here.
When we add the page to iPad or iPhone home screen, navigator.mediaDevices becomes undefined and unusable.
<meta name="apple-mobile-web-app-capable" content="yes">
...
// for some reason safari on mac can debug ios safari page but not ios home screen web apps
var d = 'typeof navigator : ' + typeof navigator; //object
d += 'typeof navigator.mediaDevices : ' + typeof navigator.mediaDevices; // undefined
// try alternates
d += 'typeof navigator.getUserMedia : ' + typeof navigator.getUserMedia; // undefined
d += 'typeof navigator.webkitGetUserMedia : ' + typeof navigator.webkitGetUserMedia; // undefined
status1.innerHTML = d;
We have quite similar problem. So far the only workaround we were able to do is to remove the meta tag for it to be "apple-mobile-web-app-capable" and let users to open it in Safari, where everything seems to work normally.
Update: While some earlier published changelogs and postings led me to believe that Web Apps using a manifest.json instead of apple-mobile-web-app-capable would finally have access to a proper WebRTC implementation, unfortunately this is not true, as others here have pointed out and testing has confirmed. Sad face.
Sorry for the inconveniences caused by this and let's hope that one lucky day in a galaxy far, far away Apple will finally give us camera access in views powered by (non-Safari) WebKit...
Yes, as others have mentioned, getUserMedia is only available directly in Safari but neither in a UIWebView nor WKWebView, so unfortunately your only choices are
removing <meta name="apple-mobile-web-app-capable" content="yes"> so your 'app' runs in a normal Safari tab, where getuserMedia is accessible
using a framework like Apache Cordova that grants you access to a device's camera in other ways.
Here's to hoping Apple removes this WebRTC restriction rather sooner than later...
Source:
For developers that use WebKit in their apps, RTCPeerConnection and RTCDataChannel are available in any web view, but access to the camera and microphone is currently limited to Safari.
Good news! The camera finally seems to be accessible from a home screen web app in the first iOS 11.3 beta.
I have made a repo with a few files, which demonstrate that it works:
https://github.com/joachimboggild/uploadtest
Steps to test:
Serve these files from a website accessible from your phone
Open the index.html in iOS Safari
Add to home screen
Open app from home screen. Now the web page is open in full screen, without navigation ui.
Press the file button to select an image from camera.
Now the camera should work normally and not be a black screen. This demonstrates that the functionality works again.
I must add that I use a plain field, not getUserMedia or somesuch. I do not know if that works.
Apparently is solved in "ios 13 beta 1":
https://twitter.com/ChromiumDev/status/1136541745158791168?s=09
Update 20/03/2020: https://twitter.com/firt/status/1241163092207243273?s=19
This seems to be working again in iOS 11.4 if you are using a file input field.
Recently I faced the same problem, the only solution I came up with was to open in the app in browser instead of the normal mode. But only on iOS!
The trick was to create 2 manifest.json files with different configurations.
The normal one for android and one for everything is Apple, manifest-ios.json, the only difference will be on the display property.
Step 1: Add id to the manifest link tag:
<link id="manifest" rel="manifest" href="manifest.json">
Step 2: Added this script to the bottom of the body:
<script>
let isIOS = /(ipad|iphone|ipod|mac)/g.test(navigator.userAgent.toLowerCase());
let manifest = document.getElementById("manifest");
if (isIOS)
manifest.href = 'manifest-ios.json'
</script>
Step 3: in the manifest-ios.json set the display to browser
{
"name": "APP",
"short_name": "app",
"theme_color": "#0F0",
"display": "browser", // <---- use this instead of standard
...
}
Another problem appears such as opening the app multiple times in multple tabs, sometimes.
But hope it helps you guys!

How to detect that browser is running in Safari on iOS 11 via JavaScript?

I want to detect that my JS code in a webpage is running in Safari on iOS 11, which supports some new features.
I can do something like
if (window.navigator.userAgent.includes('OS 11_0')) {
// iOS 11
}
but I believe this is considered unreliable.
Is there some feature or a hack that works only on iOS 11 and not on other OS and can be used to detect that version without inspecting the userAgent?
Update: I am talking about getUserMedia so I am not sure if ther is a way to test it presence without triggering the microphone permission request.
Check out this solution, and then you could do something like this:
ver = iOSversion();
if (ver[0]==11) {
// do something
}
The shared snippet can also be used to detect any specific iOS version, >iOS 2.

How to customize the message "Changes you made may not be saved." for window.onbeforeunload?

I am testing in Google Chrome.
I did some search and found that someone is using:
window.onbeforeunload = function() {
if (hook) {
return "Did you save your stuff?"
}
}
But when I use it, I still got the "Changes you made may not be saved." message. How can I change it to something I want?
You can't, the ability to do this was removed in Chrome 51. It is widely considered a security issue, and most vendors have removed support.
Custom messages in onbeforeunload dialogs (removed):
A window’s onbeforeunload property may be set to a function that returns a string. If the function returns a string, then before unloading the page, a dialog is shown to have the user confirm that they indeed want to navigate away. The string provided by the function will no longer be shown in the dialog. Rather, a generic string not under the control of the webpage will be shown.
Comments
This shipped in Safari 9.1, and has been shipping in Firefox since Firefox 4. Safari considers this a security fix and assigned it CVE-2009-2197 (see https://support.apple.com/en-us/HT206171 ). Approved with the intent https://groups.google.com/a/chromium.org/d/msg/blink-dev/YIH8CoYVGSg/Di7TsljXDQAJ .
Specification
Established standard
Status in Chromium
Removed (launch bug) in:
Chrome for desktop release 51
Chrome for Android release 51
Android WebView release 51
Opera release 38
Opera for Android release 38
In my Vue 2 application, I was able to use: window.onbeforeunload = null; in mounted() and popup does not open after submitting a form.

Categories