Skip to content

Commit d3bd689

Browse files
committed
fix: Add throttle guard and documentation to Service Worker version sync (#8695)
1 parent bc520d7 commit d3bd689

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

src/worker/Manager.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ class Manager extends Base {
148148
!Neo.insideWorker && me.createWorkers();
149149

150150
if (navigator.serviceWorker) {
151+
// Bind the message handler globally to ensure even "unmanaged" apps (those not using the SW addon)
152+
// can receive critical recovery commands (like 'reloadWindow') from the Service Worker.
153+
//
154+
// CRITICAL: We must bind this even if Neo.config.useServiceWorker is false.
155+
// Scenario: User visits App A (uses SW), then navigates to App B (no SW). The SW from App A
156+
// *still controls* App B (if in scope). If App B hits a version mismatch (404), the SW will
157+
// send a 'reloadWindow' command. App B must be listening to execute this recovery command,
158+
// otherwise it will crash with a blank page.
151159
navigator.serviceWorker.onmessage = me.onWorkerMessage.bind(me);
152160
me.checkServiceWorkerVersion()
153161
}
@@ -183,6 +191,12 @@ class Manager extends Base {
183191
}
184192

185193
/**
194+
* Proactively checks if the controlling Service Worker's version matches the client's version.
195+
* This is the primary defense against "Zombie Apps" (stale client code running against a new Service Worker).
196+
*
197+
* If a mismatch is detected, it forces a hard reload to fetch fresh assets.
198+
* Includes a throttle mechanism (via sessionStorage) to prevent infinite reload loops in case of persistent mismatches.
199+
*
186200
* @returns {Promise<void>}
187201
*/
188202
async checkServiceWorkerVersion() {
@@ -192,6 +206,18 @@ class Manager extends Base {
192206
});
193207

194208
if (swVersion?.version && swVersion.version !== Neo.config.version) {
209+
const
210+
key = 'neoVersionReload',
211+
lastReload = parseInt(sessionStorage.getItem(key) || '0'),
212+
now = Date.now();
213+
214+
if (now - lastReload < 5000) {
215+
console.error('Reload loop detected. Aborting version enforcement.');
216+
return
217+
}
218+
219+
sessionStorage.setItem(key, String(now));
220+
195221
console.error(`Version Mismatch! Client: ${Neo.config.version}, SW: ${swVersion.version}. Reloading.`);
196222
location.reload(true)
197223
}

src/worker/ServiceBase.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class ServiceBase extends Base {
4949
*/
5050
mixins: [RemoteMethodAccess],
5151
/**
52+
* Flag to enable the automatic 404 recovery strategy.
53+
* If true, 404 errors on guarded paths will trigger a client reload.
5254
* @member {Boolean} reloadOn404=true
5355
*/
5456
reloadOn404: true,
@@ -265,6 +267,12 @@ class ServiceBase extends Base {
265267
}
266268

267269
/**
270+
* Intercepts 404 errors on guarded paths (dist/production, src) to detect version mismatches.
271+
* If a mismatch is suspected (old app asking for deleted asset), triggers a forced client reload.
272+
*
273+
* Use Case: "Reactive Recovery" for when the Boot-Time check passes (or hasn't run) but the
274+
* environment changes mid-session (Atomic Deployment deletes assets).
275+
*
268276
* @param {ExtendableMessageEvent} event
269277
*/
270278
async on404(event) {

0 commit comments

Comments
 (0)