Registered or not HTML custom URL scheme detection - javascript

In my site I have a custom URL which triggers an installed app, for example
Click
If my Windows app has registered the URL scheme, Chrome prompts the user to open it.
The question is, if the app is not registered, is there a way for me to find it and prompt the user to install it?
Thanks a lot.

I found that window.onblur seems to be called if you attempt to open a scheme link and the handler is installed. This is because it either opens the registered app or opens a box asking if you want to open the app, both of which take the focus. If it's not installed the focus stays where it was.
This worked for me on Windows 10/Chrome, macOS/Chrome, android/Chrome, android/Silk & Linux/Firefox.
call tryScheme("yourScheme://blahblah", function() { <it's not installed code> })
var blurred = false;
window.onblur = function() {
blurred = true;
}
function tryScheme(url, fallbackFunction) {
blurred = false;
document.location = url;
// If we haven't lost focus in a fairly short time, call it as not installed.
setTimeout(function(){
if (blurred === true) {
console.log("Focus away from page - the scheme's probably installed");
return;
}
console.log("Still on page - the scheme's probably not installed");
fallbackFunction();
}, 1000);
}

Related

How to remember user negative decision at 'beforeinstallprompt' event while asking to install PWA?

I've just made the changes to make Chrome recognize my web application as a progressive web application. I followed the official tutorial at Chrome's Developers website:
https://developers.google.com/web/fundamentals/app-install-banners/?hl=es
So I have:
var deferredPrompt;
window.addEventListener('beforeinstallprompt', function(e) {
// Prevent Chrome 67 and earlier from automatically showing the prompt
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera
Mini/i.test(navigator.userAgent)) {
e.preventDefault();
$("#modalInstallPWA").modal('show');
} // Stash the event so it can be triggered later.
deferredPrompt = e;
}); // Installation must be done by a user gesture! Here, the button click
$("#btn-install-pwa").on('click', function() {
// Show the prompt
deferredPrompt.prompt(); // Wait for the user to respond to the prompt
deferredPrompt.userChoice.then(function(choiceResult) {
console.log(choiceResult.outcome);
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
So what I would like to is to remember user decision so Chrome is not asking the user over and over again to install the PWA. This works well when the user decided to install the PWA but not in the case the user decided to not install it.
I thought in doing this by the use of a cookie but I wanted to know if maybe there is a built-in feature or a easier way to achieve this.

check if user has already installed PWA to homescreen on Chrome?

I'm trying to create an "Add To Home Screen" button on my progressive web app, as described in Chrome's documentation.
I'm generally following the prescribed pattern, where I have some hidden button which is displayed when Chrome's beforeinstallprompt event fires.
I capture the event once it fires, and then use the event to begin the native install dialogue once my own install button is clicked. The sample code is below:
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI notify the user they can add to home screen
btnAdd.style.display = 'block';
});
btnAdd.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
btnAdd.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice
.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
The issue I'm running into is that I don't want to show my install button (btnAdd) if the user has already installed the web app to thier home screen, and I'm having trouble figuring out how to check for that scenario.
I was hoping to modify the above code as follows:
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// If the user has not already installed...
deferredPrompt.userChoice
.then(choiceResult => {
if (choiceResult === undefined) {
// Update UI notify the user they can add to home screen
btnAdd.style.display = 'block';
}
});
});
So that the install button won't be displayed if the user has already installed. But this doesn't seem to work. It appears that if they haven't made a choice already, accessing userChoice just prompts the user directly with the native dialogue.
I'm not really sure how the beforeinstallevent works, so this might not even be a good strategy. Ideally I was hoping this would work something like something like navigator.serviceWorker.ready(), which returns a Promise rather than using browser events to try and figure out when stuff is ready.
In any case, are there any ideas on how I can check that the user has installed to home screen before I show my own home screen install button?
Edit: As Mathias has commented, checking for the event before showing the button should be sufficient. I believe the issue I was having is a result of using localhost, which appears to continually fire the beforeinstallprompt event even after installation, which is not the intended behavior. Hosting the code solved the issue.
Perhaps, don't show the button until you intercept the automatic pop-up?
or
In your code, check to see if the window is standalone
If it is, you need not show the button
if (window.matchMedia('(display-mode: standalone)').matches) {
// do things here
// set a variable to be used when calling something
// e.g. call Google Analytics to track standalone use
}
My example tester here
https://a2hs.glitch.me
Source code for my tester
https://github.com/ng-chicago/AddToHomeScreen
To answer original question. With latest versions of Chrome you can use window.navigator.getInstalledRelatedApps(). It returns a promise with an array of installed apps that your web app specifies as related in the manifest.json. To enable this to work you need to add related_applications field to manifest.json
"related_applications": [{
"platform": "webapp",
"url": "https://app.example.com/manifest.json"
}]
And then you can use it like:
//check if browser version supports the api
if ('getInstalledRelatedApps' in window.navigator) {
const relatedApps = await navigator.getInstalledRelatedApps();
relatedApps.forEach((app) => {
//if your PWA exists in the array it is installed
console.log(app.platform, app.url);
});
}
Source: API docs
Now you can display some elements depending if your app is installed. E.g: you can display "Open app" button and redirect user to PWA. But remember to disable it when the user is already in the app using #Mathias's answer and checking (display-mode: standalone)
However, regarding your use case. You should display install button only when beforeinstallprompt is intercepted. Browser does not fire this event if the PWA is already installed on the device. And when prompt is fired and choiceResult.outcome === 'accepted' you hide the button again.
I don't see how this is the correct answer, because this is basically a check if user uses the App already, but the behavior we wan't is "When the user is on the web and tries to install the app again to tell him that he already has the app in his device". Upon me this is not an answer that solves this.
What we can do is:
1. When the user clicks install but has the application on his device
In this case the beforeinstallprompt event WON'T BE fired so this event will return null. We store the result in global variable and when the result is null we show this to user that he already has the app installed.
2. When the user clicks install but doesn't have the application on his device
In this case the beforeinstallprompt event WILL be fired so this event will return access to show the prompt.
We can store the result in global variable and if it is not NULL (which won't be) because beforeinstallprompt will be fired if the user don't have the app on his device we show the prompt() to the user.
I doubt if mine solution is good too but I think that the Question and the correct answer don't have nothing in common
window.addEventListener("beforeinstallprompt", event => {
window.deferedPrompt = event;
});
handleButtonClick = () => {
const promptEvent = window.deferedPrompt;
if(!promptEvent){
// DO SOMETHING
}
//Show the add to home screen prompt
promptEvent.prompt()
promptEvent.userChoice.then((result: any) => {
// Reset the deferred prompt variable, since
// prompt() can only be called once.
window.deferedPrompt = null;.
});
}
<button onClick={handleButtonClick}>Install</button>
Here is the simple function that tells you, this app is open in browser or in pwa.
original source link
function getPWADisplayMode() {
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (document.referrer.startsWith('android-app://')) {
return 'twa';
} else if (navigator.standalone || isStandalone) {
return 'standalone';
}
return 'browser';
}
HTML
<!-- start with hidden button -->
<button id="install" style="display:none;">install</button>
JAVASCRIPT
// variable store event
window.deferredPrompt = {};
// get button with id
const install_button = document.querySelector('#install');
// if the app can be installed emit beforeinstallprompt
window.addEventListener('beforeinstallprompt', e => {
// this event does not fire if the application is already installed
// then your button still hidden ;)
// show button with display:block;
install_button.style.display = 'block';
// prevent default event
e.preventDefault();
// store install avaliable event
window.deferredPrompt = e;
// wait for click install button by user
install_button.addEventListener('click', e => {
window.deferredPrompt.prompt();
window.deferredPrompt.userChoice.then(choiceResult => {
if (choiceResult.outcome === 'accepted') {
// user accept the prompt
// lets hidden button
install_button.style.display = 'none';
} else {
console.log('User dismissed the prompt');
}
window.deferredPrompt = null;
});
});
});
// if are standalone android OR safari
if (window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true) {
// hidden the button
install_button.style.display = 'none';
}
// do action when finished install
window.addEventListener('appinstalled', e => {
console.log("success app install!");
});
Another way I've found to do this is to use IndexedDB. I import "idb-keyval" (https://www.npmjs.com/package/idb-keyval) to make it simple to get/set to the IndexedDb. Then I store a value that gets checked on the next page load. One difference with this method, is that it will let you check if your application is installed already if you visit the application webpage from the browser instead of the installed app shortcut.
import * as IDB from "idb-keyval";
let deferredPropt;
// Get the stored value from the IndexedDb
IDB.get("isInstalled").then((val) => {
if (val) {
// If it exists, run code based on an installed pwa
} else {
log({ isInstalled: false });
}
});
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
deferredPrompt = e;
document.getElementById("installApp").addEventListener("click", async () => {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome == "accepted") {
// Set the value into the IndexedDb on installation of the PWA
IDB.set("isInstalled", true);
}
});
});

Mozilla WebExtension desktop notification display time and/or require user interaction

I have a WebExtension for Mozilla, which notifies me with the desktop notification function.
It works exactly how I want, but Firefox will close the notification automatically after X seconds. Is it possible to display the notification until the user clicks on it?
The thing I did is to close and reopen the notification every 5 seconds, so the user has to click on it to close it permanently.
This looks like this:
// This is the notification function
function notifyMeFunction() {
var notification = new Notification('Alert', {
icon: chrome.extension.getURL('icons.png'),
body: "New Notification",
tag: "DesktopNotification",
});
notification.onclick = function(event) {
notificationClicked = true;
}
notification.onclose = function(event) {
notificationClicked = true;
}
}
// Function which will self-open every 5 seconds
function notifyMe() {
if (notificationClicked == false) {
notifyMeFunction();
setTimeout(notifyMe, 5000);
} else {
notificationClicked = false;
}
}
Any ideas how to set the display time to something like "must interact"?
There is, currently (Firefox version <= 51.0a1), no method of indicating to the API that the user must interact with these notifications. Nor is there any way to specify for how long the notification is displayed to the user.
Note: You are using the Web Notifications API, not the WebExtensions chrome.notifications API. Neither has a way to require user interacting in Firefox.
From Chrome 50, Google Chrome does have an option to require that the user must interact with the notification: requireInteraction. Thus, at some point in time, Firefox will probably support such an option.
However, at least as of this point in time, the string requireInteraction does not exist in the Firefox source code.

Is there a way to tell if a browser is active?

On my site I have an important notification that prompts a native alert() in order to bring the site to the foreground if the window is not already focused. The problem is that on some browsers, if the user is in another desktop application (Photoshop, Microsoft Word, etc), it will not bring the browser on top of that application. In this case the alert is pretty much useless and I would like to omit it (since it blocks the other scripts on my page).
Is there a way to tell that a browser is the active application on a device? Or is there a different, non-blocking way to bring a window to the foreground?
Thanks!
Clarifications:
I already know how to check if a window is active within a browser, but I just don't know how to check if the browser application itself is active.
Also, browsers I need to support are Chrome, Safari, Firefox, and IE >= 9
You can use the Page Visibility API for this.
It is compatible with IE 10+.
Small example of code:
document.addEventListener("visibilitychange", function(e) {
console.log("visibility changed!", e);
if (document.visibilityState === 'visible') {
console.info("window is visible now!");
}
else {
console.info("something else, maybe you changed tab or minimized window!");
}
console.log("check the visibilityState property on document object for more info");
});
This will work even if the user minimizes the browser while the tab is open, so I guess this suits your needs :)
You should use the new Notification object. It works even when the browser is not focused, and is useful for sending the user important notifications.
Example: http://jsfiddle.net/howderek/792km8td/
document.getElementById('notif').onclick = function () {
function notify () {
var notification = new Notification('Test!');
}
if (Notification.permission === "granted") {
setTimeout(notify, 5000);
} else {
Notification.requestPermission(function () {
if (Notification.permission === "granted") {
setTimeout(notify, 5000);
}
});
}
}
https://developer.mozilla.org/en-US/docs/Web/API/notification
Have a global variable storing whether the window is active or not:
var window_active = true;
Now add event listeners to "listen" for window (de)activation:
window.onblur = function () {
window_active = false;
};
window.onfocus = function () {
window_active = true;
};
And when you call the alert function, check that global variable:
if (window_active)
alert("notification");
I want to mention that if you change the tab or click the url-bar, the window will be deactivated, which might be not what you want.

How can I load a new page when the iPhone/Android browser is reopened?

Okay, so I don't exactly even know how to word this question properly as you can probably tell. Hence I couldn't find any solution on Google, so I came here.
Basically, the process unfolds like this:
1.) The user logs into my site.
2.) The user uses my site, then leaves the browser and does whatever else, without killing the browser.
3.) The user opens up the browser again to check my site again.
When 3.) triggers, I want my site to detect that and forward the browser to some other page.
Does this make any sense? If you need clarification, just let me know. I believe there has to be a way to do this with jQuery or maybe just plain JS.
Thanks!
function onBlur() {
document.body.className = 'blurred';
};
function onFocus(){
document.body.className = 'focused';
};
if (/*#cc_on!#*/false) { // check for Internet Explorer
document.onfocusin = onFocus;
document.onfocusout = onBlur;
} else {
window.onfocus = onFocus;
window.onblur = onBlur;
}
Reference: Is there a way to detect if a browser window is not currently active?

Categories

Resources