Multiple service worker updates do not result in multiple `waiting` events
michaelowolf opened this issue · comments
Library Affected:
workbox-window@7.0.0
Browser & Platform:
Brave Version 1.60.118 Chromium: 119.0.6045.163 (Official Build) (x86_64)
(Probably all browsers)
Issue or Feature Request Description:
After the first update to a service worker is found, further updates that supersede it do not have waiting
events fired for them (although the registration does fire updatefound
). It's not clear to me whether this is intended or not based on the documentation, but there doesn't seem to be any indication that this should only ever happen once.
There seems to be a general assumption in workbox-window that once one service worker update is found, no more need to be handled. This is my guess based on the fact that as soon as an external service worker (which I understand to mean an updated version) is found during an update, the updatefound
listener is removed:
This means we don't get _onUpdateFound
called for new updates, and so never add the listener for their statechange
event:
I don't have a reproduceable link but can create one if helpful.
Some rough logs for this (where I've added extra app
logs when we call registration.update
and handle events):
# Load page with an existing service worker
workbox Successfully registered service worker. /sw.js
workbox A service worker with the same script URL is already controlling this page.
# First update attempt
[app] Calling `registration.update`
# Update found and `waiting` event dispatched
[app] Registration fired `updatefound`
workbox An external service worker has installed. You may want to suggest users reload this page.
[app] `waiting` (external) event fired
workbox An external service worker has installed but is waiting for this client to close before activating...
# Second update attempt
[app] Calling `registration.update`
# Update found
[app] Registration fired `updatefound`
# but no further events triggered
This assumption is acceptable in a "happy path" scenario (at least in our use case), because once an update is available for the user we show a permanent banner to accept it. Even if new updates are found and installed, the absence of the waiting
event doesn't matter because once the user accepts the banner then the latest waiting service worker will be told to skip waiting:
I suspect that some state internal to the Workbox instance may become out of sync with the registration when this happens, but I haven't seen any specific effects of that.
Where this does become a problem is recovering from update installation failures. If the first found update fails to install, that service worker never switches state to installed
and so, correctly, no waiting
event is fired. Then when a new update is found that installs correctly, still no waiting
event is fired. This has meant we cannot ever then show the user an update prompt in response to the waiting
event if the first update fails to install.
I've managed to reliably reproduce this while making use of the subresource integrity feature, by having a precached resource in the first update fail the integrity check, causing installation to fail. Trying to update again after integrity is fixed will correctly install the service worker update and put it into the waiting
state, but no waiting
event is fired for it.
Some rough logs for this occurrence:
# Load page with an existing service worker
workbox Successfully registered service worker. /sw.js
workbox A service worker with the same script URL is already controlling this page.
# First update attempt
[app] Calling `registration.update`
# Update found but integrity check fails for precached resources, so no `waiting` event fired
[app] Registration fired `updatefound`
Failed to find a valid digest in the 'integrity' attribute for resource '<URL>' with computed SHA-512 integrity '<HASH>'. The resource has been blocked.
Unknown error occurred while trying to verify integrity.
StrategyHandler.js:160 Uncaught (in promise) TypeError: Failed to fetch
at l.fetch (StrategyHandler.js:160:35)
at u.st (PrecacheStrategy.js:143:40)
at u._handle (PrecacheStrategy.js:71:31)
at async u.Rt (Strategy.js:144:13)
fetch @ StrategyHandler.js:160
st @ PrecacheStrategy.js:143
_handle @ PrecacheStrategy.js:71
# Second update attempt
[app] Calling `registration.update`
# Update found
[app] Registration fired `updatefound`
# but no further events triggered
In terms of workarounds or solutions, I suspect some combination of the redundant
event and the creation & registeration of a new Workbox instance may work, but it doesn't seem like an ideal solution.