20

I'm doing around 1-2 notifications a day and it's important the user doesn't miss it. Is there a way of removing the auto close and only allowing the user to manually close the notification?

I don's see any option for this in the Notification Options:

http://developer.chrome.com/extensions/notifications.html#type-NotificationOptions

gkalpak
  • 47,844
  • 8
  • 105
  • 118
user1255162
  • 1,258
  • 2
  • 9
  • 13

5 Answers5

21

Notification now has (since Chrome 50) a requireInteraction property to force the notification to stay on screen:

var notification = new Notification("TITLE", {
          icon: 'assets/res/icon.png',
          body: "MESSAGE",
          requireInteraction: true     
});

In onclick you have to close the notification:

notification.onclick = function()
{
    this.close();
}
Xan
  • 74,770
  • 16
  • 179
  • 206
  • The requireInteraction:true advice is correct but you do not have to manually close the notification in an onclick handler. The notifications all have a Close button that works out of the box. – Katie Aug 22 '18 at 19:42
13

UPDATE (2016-05-24):

Xan commented:

Fun fact: all this arcane hacking is no longer needed; see the new requireInteraction flag

It is availalbe since Chrome 50. More info.


Thanks to root's comment, this answer is revised to account for the fact that the onClosed event is not fired when the notification disappears (into the notigications area) after a few seconds. This is still kind of a hacky solution.


Background

You can take advantage of the fact that a notification's life-cycle ends with one of the following events:

  • onClosed: When the user clicks on the small 'x' in the top-right corner.
  • onClicked: When the user clicks on the message body (not the 'x', not some button).
  • onButtonClicked: When the user clicks on one of the buttons (if any).

The solution

The proposed solution consists of the following steps:

  1. Register listeners for all the events mentioned above.
  2. Register a timeout after a few seconds (e.g. 30) -after the notification is hidden- that will delete and re-create the notification (so it effectively stays visible on the screen.
  3. If any of the listeners set on step 1 fires, it means the user interracted with the notification, so cancel the timeout (you don't need to re-create the notification).

Writing about it is simple, coding takes some more effort :) Here is the sample code I used to achieve what is described above:

In manifest.json:

{
    "manifest_version": 2,
    "name":    "Test Extension",
    "version": "0.0",

    "background": {
        // We need this for the `Timeout` - see notes below
        "persistent": true,
        "scripts": ["background.js"]
    },

    "browser_action": {
        "default_title": "Test Extension"
        "default_icon": {
            "19": "img/icon19.png",
            "38": "img/icon38.png"
        },
    },

    "permissions": ["notifications"]
}

In background.js:

var pendingNotifications = {};

/* For demonstration purposes, the notification creation
 * is attached to the browser-action's `onClicked` event.
 * Change according to your needs. */
chrome.browserAction.onClicked.addListener(function() {
    var dateStr = new Date().toUTCString();
    var details = {
        type:    "basic",
        iconUrl: "/img/notifIcon.png",
        title:   "REMINDER",
        message: dateStr + "\n\n"
                 + "There is one very important matter to attend to !\n"
                 + "Deal with it now ?",
        contextMessage: "Very important stuff...",
        buttons: [
            { title: "Yes" }, 
            { title: "No"  }
        ]
    };
    var listeners = {
        onButtonClicked: function(btnIdx) {
            if (btnIdx === 0) {
                console.log(dateStr + ' - Clicked: "yes"');
            } else if (btnIdx === 1) {
                console.log(dateStr + ' - Clicked: "no"');
            }
        },
        onClicked: function() {
            console.log(dateStr + ' - Clicked: "message-body"');
        },
        onClosed: function(byUser) {
            console.log(dateStr + ' - Closed: '
                        + (byUser ? 'by user' : 'automagically (!?)'));
        }
    };

    /* Create the notification */
    createNotification(details, listeners);
});

/* Create a notification and store references
 * of its "re-spawn" timer and event-listeners */
function createNotification(details, listeners, notifId) {
    (notifId !== undefined) || (notifId = "");
    chrome.notifications.create(notifId, details, function(id) {
        console.log('Created notification "' + id + '" !');
        if (pendingNotifications[id] !== undefined) {
            clearTimeout(pendingNotifications[id].timer);
        }

        pendingNotifications[id] = {
            listeners: listeners,
            timer: setTimeout(function() {
                console.log('Re-spawning notification "' + id + '"...');
                destroyNotification(id, function(wasCleared) {
                    if (wasCleared) {
                        createNotification(details, listeners, id);
                    }
                });
            }, 10000)
        };
    });
}

/* Completely remove a notification, cancelling its "re-spawn" timer (if any)
 * Optionally, supply it with a callback to execute upon successful removal */
function destroyNotification(notifId, callback) {

    /* Cancel the "re-spawn" timer (if any) */
    if (pendingNotifications[notifId] !== undefined) {
        clearTimeout(pendingNotifications[notifId].timer);
        delete(pendingNotifications[notifId]);
    }

    /* Remove the notification itself */
    chrome.notifications.clear(notifId, function(wasCleared) {
        console.log('Destroyed notification "' + notifId + '" !');

        /* Execute the callback (if any) */
        callback && callback(wasCleared);
    });
}

/* Respond to the user's clicking one of the buttons */
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
    if (pendingNotifications[notifId] !== undefined) {
        var handler = pendingNotifications[notifId].listeners.onButtonClicked;
        destroyNotification(notifId, handler(btnIdx));
    }
});

/* Respond to the user's clicking on the notification message-body */
chrome.notifications.onClicked.addListener(function(notifId) {
    if (pendingNotifications[notifId] !== undefined) {
        var handler = pendingNotifications[notifId].listeners.onClicked;
        destroyNotification(notifId, handler());
    }
});

/* Respond to the user's clicking on the small 'x' in the top right corner */
chrome.notifications.onClosed.addListener(function(notifId, byUser) {
    if (pendingNotifications[notifId] !== undefined) {
        var handler = pendingNotifications[notifId].listeners.onClosed;
        destroyNotification(notifId, handler(byUser));
    }
});

Final notes:

  • If your notifications are extremely important, you should implement a "recovery" mechanism, for case such as browser os OS crashes or abrupt termination. E.g. relying on a more persistent storage (localStorage, chrome.storage API etc), resuming pending notifications on extension/browser start-up etc.
  • It might be a good idea to put a limit on the total number of pending notifications, for "user-friendliness" reasons. If your pending notifications exceed, say, 3 at any given moment, you could replace them with one that informs there are pending notifications and direct the user to a page where you list all of them. (The code will be considerably more complex, but hey anything for the user, right ? ;)
  • Instead of trying to keep the notifications visible on screen until the user decides to deal with them, it could be better using a Badge (wich can have a color and a small text - indicating the number of pending notifications.
  • I haven't looked into it, but it might be possible (in which case it is advisable as well) to replace the Timeout's with the chrome.alarms API and then convert the background-page to non-persistent (a.k.a. event-page) which will make it more resource-friendly.
Community
  • 1
  • 1
gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • this event doesn't seem to be fired when notification goes away from the screen after 5 seconds. I guess it is not counted as "closed" as you can still see it IF you click on sys tray icon – root Dec 02 '13 at 23:51
  • @root: You were write :/ I revised my answer. Thx for the heads-up. – gkalpak Dec 04 '13 at 08:10
  • nice one. how do you match respawn timeout with the chrome hide timeout to give impression it's a persistent notification rather than "keeps popping up"? – root Dec 05 '13 at 18:09
  • You could made the refresh timeout sufficiently short (e.g. 5 secs), but I believe it is unnecessary overhead. The user is either away (so it doesn't matter how often we re-spawn the notification until (s)he comes back) or (s)he is there but deliberately chooses to ignore our notification (so shoosing a very short refresh interval would be rather annoying). In would suggest something between 30 secs and 5 mins (based on how "urgent" the notification is). – gkalpak Dec 05 '13 at 18:54
  • [We](https://idorecall.com)'re using the alarms technique to trigger periodic notifications and it works well. chrome.alarms.create and `chrome.alarms.onAlarm.addListener`. – Dan Dascalescu Sep 10 '15 at 10:14
  • 1
    Fun fact: all this arcane hacking is no longer needed; see the new `requireInteraction` flag. – Xan May 24 '16 at 13:28
4

This answer is obsolete; see this answer for an up to date solution involving requireInteraction flag (Chrome 50+).


There is a slightly better (but again, hacky) solution.

When you call update on a notification that changes its priority, and the priority is 0 or above, the notification will be re-shown and the timer for hiding it reset.

So, you can show a notification with a high priority, (say, 2) and then repeat this on an interval shorter than time to hide the notification:

chrome.notifications.update(id, {priority : 1}, function(wasUpdated) {
  if(wasUpdated) {
    chrome.notifications.update(id, {priority : 2}, function() {});
  } else {
    // Notification was fully closed; either stop updating or create a new one
  }
});
Community
  • 1
  • 1
Xan
  • 74,770
  • 16
  • 179
  • 206
4

UPDATE answar: after Chrome 50, please add new attribute: [requireInteraction: true]!

don't use chrome.notifications.create

try to use var notification= new Notification("New mail from John Doe", { tag: 'msg1', requireInteraction: true});
will not to close.

if want to close=> notification.close();

ref: http://www.w3.org/TR/notifications/

https://developer.mozilla.org/en-US/docs/Web/API/notification/requireInteraction

user909016
  • 53
  • 5
3

webkit notifications seem to be more persistent on the screen, althouth they have the opposite problem - how to hide them to sys tray.

http://developer.chrome.com/extensions/desktop_notifications.html

root
  • 2,327
  • 1
  • 22
  • 18