-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide a one-line way to listen for a waiting Service Worker #1222
Comments
I suggest this "prolyfill" (a forward-looking polyfill for behavior to be eventually standardized) if (!('waiting' in navigator.serviceWorker)) {
navigator.serviceWorker.waiting = new Promise(function(resolve) {
navigator.serviceWorker.ready.then(function(reg) {
function awaitStateChange() {
reg.installing.addEventListener('statechange', function() {
if (this.state === 'installed') resolve(reg);
});
}
if (reg.waiting) resolve(reg);
if (reg.installing) awaitStateChange();
reg.addEventListener('updatefound', awaitStateChange);
})
});
} |
Two cents. I'm not sold that I would actually prefer some way of listener for changes for the service worker that updating along the lines of: reg.addEventListener('updatestatechange', (event) => {
switch(event.state) {
case 'installing':
break;
case 'waiting':
break;
case 'activating':
break;
case 'activated':
break;
}
}); |
It's hard to beat this one-liner: This is definitely uglier. navigator.serviceWorker.ready.then(function(reg) {
reg.addEventListener('updatestatechange', function(event) {
if (event.state === 'waiting') alertUser(event.registration);
});
}); If navigator.serviceWorker.addEventListener('updatestatechange', function(event) {
if (event.state === 'waiting') alertUser(event.registration);
});
}); But at that point, it would be nicer if it were an navigator.serviceWorker.addEventListener('updatewaiting', function(event) {
alertUser(event.registration);
});
}); But then, what would I use the event for? It wouldn't be extendable or anything. Might as well just provide a promise and eliminate the event altogether.
|
Since this is related to the registration, this promise should really go on the registration. That way it can be used on all the origin's registrations. I feel that there may be a more general way to solve this. For example, what if async function waitingWorker(reg) {
if (reg.waiting) return;
return reg.on('statechange').filter(e => e.target.state === 'installed').first();
} |
A few points.
I worry that this solves non-problems without adequately addressing the real use case: refresh banners. I claim that the only use case for this is a refresh banner. I'm not aware of any use case where someone would want to track state changes on all of the origin's registrations; the only registration that matters for refresh banners is the registration for the current page's controller. My goal is to listen for a refresh banner in one easy line. A successful implementation should make us want to delete an entire quiz from the Udacity course on service workers. There's already an "easy" solution in nine lines of boilerplate; anything that makes the use case just slightly more convenient is probably not worth the trouble, IMO. Under Jake's proposal, the whole code snippet would look like this. (Note that it has to be in ES5, since it's going to run in the client-side page. navigator.serviceWorker.ready.then(function(reg) {
if (reg.waiting) return alertUser(reg);
return reg.on('statechange').filter(function(e) {
return e.target.state === 'installed';
}).first().then(function(e) {
alertUser(reg);
});
}) IMO, the observable didn't even help; it's actually a bit easier to just use navigator.serviceWorker.ready.then(function(reg) {
if (reg.waiting) return alertUser(reg);
return reg.addEventListener('statechange', function(e) {
if (e.target.state === 'installed') alertUser(reg);
});
}) This is an improvement over the status quo, but the cow path is only half paved. It has to repeat navigator.serviceWorker.waiting.then(alertUser); Being DRY allows users to define the navigator.serviceWorker.waiting.then(function(reg) {
var button = document.createElement('button');
button.textContent = 'This site has updated. Please click here to see changes.');
button.style.position = 'fixed';
button.style.bottom = '24px';
button.style.left = '24px';
button.addEventListener('click', function() {
if (reg.waiting) {
button.disabled = true;
reg.waiting.postMessage('skipWaiting');
}
});
document.body.appendChild(button);
}); |
It could also be said that creating We have to be very careful with single use-case APIs. Big assumptions about what everyone wants, and single use-case high-level APIs can often end up being wasted effort. This is what happened with appcache. I realise that a single function to do the thing you want would help you, but I'd rather dig into it, and find something that will help with lots of cases. |
When you say "dig into it," I guess you mean you'd want to investigate how the relatively low-level I'd be happy to help with such an investigation. I know of no way to access data like that, but I think I've seen Googlers exhibit data like that in the past...? |
That would be excellent data, but I already know folks also track workers that enter the "redundant" state for error tracking. |
Seems like you've got all the cards here… are there other use cases for |
Actually, let me pose a more specific question. There are only five states:
This issue is about tracking the Are you aware of anybody who pays attention to the |
Minor note: I think oncontrollerchange is actually for activating and not activated. I added a 10 second timeout to the activate callback in the service worker and the |
Correct. Controller is set when activate event is dispatched. FetchEvents, etc won't be dispatched until the activate event waitUntil is resolved, though. |
This API is full of surprises! I think I got confused because the the new service worker is the So, uh, Am I now right in understanding that the only known reasons to listen for the |
That's my understanding. Waiting for activated would actually be better before refreshing the page - otherwise, the page refreshes the browser just acts as if it's loading the page while it's waiting for the service worker to activate. Other use case I'll throw in the ring is testing. May be useful to know what a service worker is in any of these use cases. Redundant may also be useful in certain scenarios such as libraries managing service workers on behalf of the user (i.e. push SDKs) |
Here'a a late suggestion, given that this looks to be on the agenda for tomorrow's F2F: would exposing the Code that I've used in the past to in the This doesn't provide what initial requestor was asking for, in that you still need to listen for event changes. But it at least would expose one extra bit of useful information that would open the door to using generic |
I don't see that exposing the skip-waiting flag solves any problem at all. Either the SW skips waiting on install, in which case you probably don't want any To be clear, the problem as I see it is that refreshing the page on upgrade requires a lot of confusing boilerplate, even for users of workbox. Workbox can automate adding arbitrarily complicated code to the SW, but it can't automagically drop code in the window. So instead they have an "advanced recipe" in the documentation ("Offer a page reload to users") explaining how to allow users to refresh the page when the SW updates. https://developers.google.com/web/tools/workbox/guides/advanced-recipes The "advanced recipe" links to a blog article that I wrote on the topic. https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68 The article is 2,000 words long! Understanding this code requires understanding the registration API, the difference between the Maybe this would be OK if refreshing the page in response to SW updates was an advanced use case that most sites don't need, but IMO refreshing on upgrade is a perfectly ordinary use case that basically everybody needs. The general feedback I see in this thread is "ah, but refreshing is just one use case." I've said that this seems off-base, because what I'm proposing is sugar, and there are no other important use cases to desugar. If it helps this proposal move forward, I think @gauntface's |
@dfabulich we're currently working on a A skip waiting flag would at least let us know when not to show such a message if we knew skip waiting had already been applied after the install phase. It's not a complete solution, but it does help with part of this. |
|
In a comment on #1247 that I think was directed at this proposal, @jakearchibald wrote:
If a promise is entirely off the table, then that entails that the best hope for an improvement here is for navigator.serviceWorker.ready.then(function(reg) {
if (reg.waiting) return alertUser(reg);
return reg.addEventListener('statechange', function(e) {
if (e.target.state === 'installed') alertUser(reg);
});
}); As I said above in this thread, this isn't what I'd prefer, but at this point, I'm going to give up on my favorite thing in favor of something (anything) getting done here. |
@dfabulich in case you're interested, we've included this feature in the With import {Workbox} from 'workbox-window';
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', () => {
// Do something when the SW is stuck in the waiting phase.
});
wb.register(); While obviously this is not as good as a native solution, hopefully it'll make it easier for developers to handle these important cases. |
Does that event fire immediately if a service worker is already waiting? That's the main confusing thing about using an event listener for this; event listeners prepare for future events, but require an alternate approach for when the event already happened before the listener attached. |
It fires if a SW was waiting at registration time, or if an update was found and installed but then that SW didn't move to the activating state.
Yeah, it's possible to miss it. If you await registration, you'd missed the case you just described. We may consider dispatching our events in the next microtask to help avoid this. |
Another year has turned... Having just spent too many hours (far too many hours) trying to get a simple update workflow working flawlessly (and I am still not there), @dfabulich's one-liner looks like heaven to me. navigator.serviceWorker.waiting.then(alertUser); Why is something that seems so simple so maddeningly complex? If there is ever an update I would like to know so I can tell the user. It might be because they pressed a 'check for updates' button. Or it might be because they launched the app and an update was found. Or some periodic background check might find an update. It doesn't matter. In all these cases I need to say: "An update has been found - would you like to use it now?" If they say yes then I need to skip waiting and reload the page when the new service worker is fully activated and in control. This has to be the most common use case of all and yet it really seems almost impossible to implement (at the moment...) |
3 years later and this is still not there? @daffinm Is absolutely right, surly this is one of the most common use case. |
It's a common use case to pop up a notification to end users when a new Service Worker is waiting, but it's inconvenient to listen for that event.
ServiceWorkerRegistration offers an
onupdatefound
event, but that fires when the new Service Worker is detected and installation has started, not when the new Service Worker is fully installed and waiting to take control. In other words, it notifies us whenregistration.installing
changes, but not whenregistration.waiting
changes.It's still possible to await a change in
registration.waiting
by attaching a listener to theonstatechange
event of the.installing
Service Worker, and to wait for an.installing
Service Worker by waiting for theonupdatefound
event of the registration itself, like this:This function gets its own special quiz in the Udacity "Offline Web Applications" course; it seems like this cow path could be paved.
I propose adding a
.waiting
Promise on the ServiceWorkerContainer, and/or anonwaiting
event to ServiceWorkerRegistration. I'd love a one-liner like this:Combined with #1016, you could write code like this:
(Uh, don't actually use
confirm()
in production; it's just convenient for this example.)The text was updated successfully, but these errors were encountered: