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.
Related
the spec says:
the user agent MUST still offer the user unlimited choice of any
display surface.
https://www.w3.org/TR/screen-capture/#dom-mediadevices
But Firefox only offers "Window" or "Screen", and not the browser tab contents like Chrome/Edge. Am I missing something? Is there a way to capture only the tab?
You're right. Firefox (and Safari 16) currently allows windows and screens only to be captured with getDisplayMedia(). Chrome allows window, screens, and tabs.
Note that you can detect afterwards which display surface type (tab, window, screen) the user picked thanks to the displaySurface track setting.
// Prompt the user to share a tab, a window or a screen.
const stream = await navigator.mediaDevices.getDisplayMedia();
const [track] = stream.getVideoTracks();
const displaySurface = track.getSettings().displaySurface;
if (displaySurface == "browser") {
// User picked a tab.
} else if (displaySurface == "window") {
// User picked a window.
} else if (displaySurface == "monitor") {
// User picked a screen.
}
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);
}
});
});
Im implementing auth using this and am currently showing a loading icon in React when a user clicks the button to sign in and the auth2 account selection/login window shows.
However if a user closes the window, there doesnt seem to be any event fired i.e the signIn() function which returns a promise never resolves, I would have thought google would return an error for this promise if the window is closed. As a result there is no way for me to stop showing the loader icon and reshow the login menu.
I was wondering if anyone had a solution for this?
I try to modifiy my code that call Google OAuth 2.0 window.
You only have to add extra AJAX method that cover what is Google OAuth error result.
gapi.auth2.getAuthInstance().signIn()
Change it to this one,
gapi.auth2.getAuthInstance().signIn().then(function(response){
//If Google OAuth 2 works fine
console.log(response);
}, function(error){
//If Google OAuth 2 occured error
console.log(error);
if(error.error === 'popup_closed_by_user'){
alert('Oh Dude, Why you close authentication user window...!');
}
});
That's it...
For more detail about Google OAuth 2.0 information, you can visit this link.
https://developers.google.com/api-client-library/javascript/samples/samples#authorizing-and-making-authorized-requests
Sample code on JavaScript:
https://github.com/google/google-api-javascript-client/blob/master/samples/authSample.html
Although the API provides a mechanism for detecting when the user clicks the Deny button, there is not a built-in way for detecting that the user abruptly closed the popup window (or exited their web browser, shut down their computer, and so on). The Deny condition is provided in case you want to re-prompt the user with reduced scopes (e.g. you requested "email" but only need profile and will let the user proceed without giving you their email).
If the response from the sign-in callback contains the error, access_denied, it indicates the user clicked the deny button:
function onSignInCallback(authResult) {
if (authResult['error'] && authResult['error'] == 'access_denied') {
// User explicitly denied this application's requested scopes
}
}
You should be able to implement sign-in without detecting whether the window was closed; this is demonstrated in virtually all of the Google+ sample apps. In short, you should avoid using a spinner as you're doing and instead should hide authenticated UI until the user has successfully signed in.
It's not recommended you do this, but to implement detection of the pop-up closing, you could do something like override the global window.open call, then detect in window.unload or poll whether the window was closed without the user authenticating:
var lastOpenedWindow = undefined;
window.open = function (open) {
return function (url, name, features) {
// set name if missing here
name = name || "default_window_name";
lastOpenedWindow = open.call(window, url, name, features);
return lastOpenedWindow;
};
}(window.open);
var intervalHandle = undefined;
function detectClose() {
intervalHandle = setInterval(function(){
if (lastOpenedWindow && lastOpenedWindow.closed) {
// TODO: check user was !authenticated
console.log("Why did the window close without auth?");
window.clearInterval(intervalHandle);
}
}, 500);
}
Note that as I've implemented it, this mechanism is unreliable and subject to race conditions.
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.
I am trying to create a notification in Chrome.
I have write this simple code but there is notification shown in CHrome whereas checkPermission() return well 0.
I do the same thing than this website (example), which works fine in my Chrome browser.
if (window.webkitNotifications.checkPermission() == 0) {
window.webkitNotifications.createNotification("icon.png", "title", "text").show();
} else {
window.webkitNotifications.requestPermission();
}
Where is the problem ?
[EDIT : Problem fixed]
In fact, i allows to show notification from all website in Chrome settings, and now, it works fine !
Request permission only work on a user gesture (see below question, and quote from the docs).
In short you need to register a click event or something similar then request permission.
document.querySelector('#show_button').addEventListener('click', function() {
if (window.webkitNotifications.checkPermission() == 0) { // 0 is PERMISSION_ALLOWED
var notification = window.webkitNotifications.createNotification(
'icon.png', 'Notification Title', 'Notification content...');
notification.show();
} else {
window.webkitNotifications.requestPermission();
}
}, false);
Here's a jsfiddle
Webkit notifications requestPermission function doesn't work
requestPermission Requests that the user agent ask the user for
permission to show notifications from scripts. This method should only
be called while handling a user gesture; in other circumstances it
will have no effect. This method is asynchronous. The function
provided in callback will be invoked when the user has responded to
the permission request. If the current permission level is
PERMISSION_DENIED, the user agent may take no action in response to
requestPermission.