23

I'm testing under Chrome Version 42.0.2311.152m and I want to implement to open a window on a notificationclick like in this example: (source: https://developer.mozilla.org/en-US/docs/Web/API/WindowClient )

self.addEventListener('notificationclick', function(event) {
  console.log('On notification click: ', event.notification.tag);
  event.notification.close();

  // This looks to see if the current is already open and
  // focuses if it is
  event.waitUntil(clients.matchAll({
    type: "window"
  }).then(function(clientList) {
    for (var i = 0; i < clientList.length; i++) {
      var client = clientList[i];
      if (client.url == '/' && 'focus' in client)
        return client.focus();
    }
    if (clients.openWindow)
      return clients.openWindow('/');
  }));
});

My filestructure is like:
https://myurl.no-ip.org/app/index.html
https://myurl.no-ip.org/app/manifest.json
https://myurl.no-ip.org/app/service-worker.js

I have the issue that I always get an

InvalidAccessError

when calling clients.openWindow('/') or clients.openWindow('https://myurl.no-ip.org/app/index.html') in the service-worker.js, I receive the error:

{code: 15,
message: "Not allowed to open a window.",
name: "InvalidAccessError"}

The "return client.focus()" line is never reached because the client.url is never just '/'. Looking at

clients.matchAll({type: "window"})
.then(function (clientList) {
console.log(clientList[0])});

I see my current WindowClient:

{focused: false,
frameType: "top-level",
url: "https://myurl.no-ip.org/app/index.html",
visibilityState: "hidden" }

The properties 'focused' and 'visibilityState' are correct and change correctly.
By doing a manual focus call

clients.matchAll({type: "window"})
    .then(function (clientList) {
    clientList[0].focus()});

I receive the error:

{code: 15,
message: "Not allowed to focus a window.",
name: "InvalidAccessError"}

I think the problem is that url is not just '/'. Do you have any ideas for that?

Thank you very much!
Best regards
Andi

user24502
  • 1,662
  • 4
  • 16
  • 21
  • I haven't seen that specific error before and I can't reproduce it myself. Chrome 43 will be the stable release soon (and already is on some platforms). Can you reproduce it there, or in Chrome dev/Canary? – Jeff Posnick May 20 '15 at 14:35
  • We have some similar code that seems to work on Chrome on Mac, but not Chrome on Windows. What OS were you testing with? – Joel Duckworth Oct 24 '16 at 02:42

2 Answers2

54

Your code works fine for me, so I'll explain the requirements for using openWindow / focus, and how you can avoid the "Not allowed to [open|focus] a window" error message.

clients.openWindow() and windowClient.focus() are only allowed after clicking the notification (in Chrome 47 at least), and at most one of these methods can be called, for the duration of the click handler. This behavior was specified in https://github.com/slightlyoff/ServiceWorker/issues/602.

If your openWindow / focus call is rejected with error message

"Not allowed to open a window." for openWindow
"Not allowed to focus a window." for focus

then you didn't satisfy the requirements of openWindow / focus. For example (all points also apply to focus, not just openWindow).

  • openWindow was called while the notification wasn't clicked.
  • openWindow was called after the notificationclick handler returned, and you did not call event.waitUntil with a promise.
  • openWindow was called after the promise passed to event.waitUntil was resolved.
  • The promise was not resolved, but it took "too long" (10 seconds in Chrome), so the temporary permission to call openWindow expired.

It is really necessary that openWindow / focus is called at most once, and before the notificationclick handler finishes.

As I said before, the code in the question works, so I'll show another annotated example.

// serviceworker.js
self.addEventListener('notificationclick', function(event) {
    // Close notification.
    event.notification.close();

    // Example: Open window after 3 seconds.
    // (doing so is a terrible user experience by the way, because
    //  the user is left wondering what happens for 3 seconds.)
    var promise = new Promise(function(resolve) {
        setTimeout(resolve, 3000);
    }).then(function() {
        // return the promise returned by openWindow, just in case.
        // Opening any origin only works in Chrome 43+.
        return clients.openWindow('https://example.com');
    });

    // Now wait for the promise to keep the permission alive.
    event.waitUntil(promise);
});

index.html

<button id="show-notification-btn">Show notification</button>
<script>
navigator.serviceWorker.register('serviceworker.js');
document.getElementById('show-notification-btn').onclick = function() {
    Notification.requestPermission(function(result) {
        // result = 'allowed' / 'denied' / 'default'
        if (result !== 'denied') {
            navigator.serviceWorker.ready.then(function(registration) {
                // Show notification. If the user clicks on this
                // notification, then "notificationclick" is fired.
                registration.showNotification('Test');
            });
        }
    });
}
</script>

PS. Service workers are still in development, so it's worth mentioning that I've verified that the above remarks are correct in Chrome 49, and that the example works in Chrome 43+ (and opening / instead of https://example.com also works in Chrome 42).

Rob W
  • 341,306
  • 83
  • 791
  • 678
  • What's the purpose of the timeout? Could you just open the window straightaway? – capouch May 19 '17 at 23:00
  • 2
    @capouch To demonstrate asynchronous behavior. – Rob W May 20 '17 at 20:35
  • 1
    Yes, the timeout is complicating things but it is also a nice way to show what else we could do. If you want to open the window right away do `event.waitUntil(clients.openWindow('page.html'));` @RobW how to find out when we need to use `waitUntil` ? – jakubiszon Jun 18 '18 at 17:01
  • 1
    What is the solution? You wrote the code of the question is correct, but it's not working! So how it can be correct? Also, your solution, don't provide any solution! – Federico Schiocchet Feb 03 '21 at 11:11
  • Obviously also the code of your example is not working – Federico Schiocchet Feb 03 '21 at 11:12
0

This worked for me

You should define Promise that will fire when your operation is finished.

Example bellow shows how to return chained Promise

First Promise returns list of windows. If it's not empty we are focusing one and returning Promise.resolve() - which is resolve immediately.

If no windows found we are returning next chained Promise - first on opens new window second tries to focus it.

addEventListener('notificationclick', (event) => {
    console.log('---- notification clicked ----')
    console.log(event)

    //using notification data to constract specific path 
    const data = event.notification.data
    console.log(data)
    let url  = 'https://exmaple.com'
    if(data){
        url += data['business'] ?
            `/business/messages/${data['respondent']}` :
            `/store/${data['respondent']}/questions`
    }

    console.log('new window url: ' + url)
    event.notification.close()
    //event should wait until we done
    event.waitUntil(
        //do we have some windows of our app?
        self.clients.matchAll({includeUncontrolled: true, type: 'window'})
            .then(list=>{
                console.log('total clients: '+list.length)
                if(list.length === 0){
                    //no windows of our app. We will open new one
                    console.log('no clients found')
                    return self.clients.openWindow(url)
                        .then((windowClient) => {
                            //we should focus new window and return Promise to terminate our event
                            return windowClient ? windowClient.focus() : Promise.resolve()
                        })
                }
                const client = list[0]
                console.log(client)
                //we have a window of our app. Let's focus it and return Promise 
                client.focus()
                console.log('--window focused---')
                return Promise.resolve()
            }))

})
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 02 '23 at 01:13