Skip to content

Commit de220c1

Browse files
committed
Unregister non-matching serviceworkers
With the addition of the /assets url, users who visited a previous version of the site now may have two active service workers, one with the old scope `/` and one with scope `/assets`. This check for serviceworkers that do not match the current script path and unregisters them. Also included is a small refactor to publicpath.js which was simplified because AssetUrlPrefix is always present now. Also it makes use of the new joinPaths helper too. Fixes: #15823
1 parent 2dc3e4e commit de220c1

File tree

4 files changed

+68
-9
lines changed

4 files changed

+68
-9
lines changed

web_src/js/features/serviceworker.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
const {UseServiceWorker, AppSubUrl, AppVer} = window.config;
1+
import {joinPaths} from '../utils.js';
2+
3+
const {UseServiceWorker, AppSubUrl, AssetUrlPrefix, AppVer} = window.config;
24
const cachePrefix = 'static-cache-v'; // actual version is set in the service worker script
5+
const workerAssetPath = joinPaths(AssetUrlPrefix, 'serviceworker.js');
36

47
async function unregister() {
58
const registrations = await navigator.serviceWorker.getRegistrations();
@@ -8,6 +11,13 @@ async function unregister() {
811
}));
912
}
1013

14+
async function unregisterOtherWorkers() {
15+
for (const registration of await navigator.serviceWorker.getRegistrations()) {
16+
const scriptURL = registration?.active?.scriptURL || '';
17+
if (!scriptURL.endsWith(workerAssetPath)) registration.unregister();
18+
}
19+
}
20+
1121
async function invalidateCache() {
1222
const cacheKeys = await caches.keys();
1323
await Promise.all(cacheKeys.map((key) => {
@@ -29,13 +39,16 @@ async function checkCacheValidity() {
2939
export default async function initServiceWorker() {
3040
if (!('serviceWorker' in navigator)) return;
3141

42+
// unregister all service workers where scriptURL does not match the current one
43+
await unregisterOtherWorkers();
44+
3245
if (UseServiceWorker) {
3346
try {
3447
// normally we'd serve the service worker as a static asset from AssetUrlPrefix but
3548
// the spec strictly requires it to be same-origin so it has to be AppSubUrl to work
3649
await Promise.all([
3750
checkCacheValidity(),
38-
navigator.serviceWorker.register(`${AppSubUrl}/assets/serviceworker.js`),
51+
navigator.serviceWorker.register(joinPaths(AppSubUrl, workerAssetPath)),
3952
]);
4053
} catch (err) {
4154
console.error(err);

web_src/js/publicpath.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
// This sets up the URL prefix used in webpack's chunk loading.
22
// This file must be imported before any lazy-loading is being attempted.
3+
import {joinPaths} from './utils.js';
34
const {AssetUrlPrefix} = window.config;
45

5-
if (AssetUrlPrefix) {
6-
__webpack_public_path__ = AssetUrlPrefix.endsWith('/') ? AssetUrlPrefix : `${AssetUrlPrefix}/`;
7-
} else {
8-
const url = new URL(document.currentScript.src);
9-
__webpack_public_path__ = url.pathname.replace(/\/[^/]*?\/[^/]*?$/, '/');
10-
}
6+
__webpack_public_path__ = joinPaths(AssetUrlPrefix, '/');

web_src/js/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ export function extname(path = '') {
99
return ext || '';
1010
}
1111

12+
// join a list of path segments with slashes, ensuring no double slashes
13+
export function joinPaths(...parts) {
14+
let str = '';
15+
for (const part of parts) {
16+
if (!part) continue;
17+
str = !str ? part : `${str.replace(/\/$/, '')}/${part.replace(/^\//, '')}`;
18+
}
19+
return str;
20+
}
21+
1222
// test whether a variable is an object
1323
export function isObject(obj) {
1424
return Object.prototype.toString.call(obj) === '[object Object]';

web_src/js/utils.test.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
basename, extname, isObject, uniq, stripTags,
2+
basename, extname, isObject, uniq, stripTags, joinPaths,
33
} from './utils.js';
44

55
test('basename', () => {
@@ -15,6 +15,46 @@ test('extname', () => {
1515
expect(extname('file.js')).toEqual('.js');
1616
});
1717

18+
test('joinPaths', () => {
19+
expect(joinPaths('', '')).toEqual('');
20+
expect(joinPaths('', 'b')).toEqual('b');
21+
expect(joinPaths('', '/b')).toEqual('/b');
22+
expect(joinPaths('', '/b/')).toEqual('/b/');
23+
expect(joinPaths('a', '')).toEqual('a');
24+
expect(joinPaths('/a', '')).toEqual('/a');
25+
expect(joinPaths('/a/', '')).toEqual('/a/');
26+
expect(joinPaths('a', 'b')).toEqual('a/b');
27+
expect(joinPaths('a', '/b')).toEqual('a/b');
28+
expect(joinPaths('/a', '/b')).toEqual('/a/b');
29+
expect(joinPaths('/a', '/b')).toEqual('/a/b');
30+
expect(joinPaths('/a/', '/b')).toEqual('/a/b');
31+
expect(joinPaths('/a', '/b/')).toEqual('/a/b/');
32+
expect(joinPaths('/a/', '/b/')).toEqual('/a/b/');
33+
34+
expect(joinPaths('', '', '')).toEqual('');
35+
expect(joinPaths('', 'b', '')).toEqual('b');
36+
expect(joinPaths('', 'b', 'c')).toEqual('b/c');
37+
expect(joinPaths('', '', 'c')).toEqual('c');
38+
expect(joinPaths('', '/b', '/c')).toEqual('/b/c');
39+
expect(joinPaths('/a', '', '/c')).toEqual('/a/c');
40+
expect(joinPaths('/a', '/b', '')).toEqual('/a/b');
41+
42+
expect(joinPaths('', '')).toEqual('');
43+
expect(joinPaths('', '/')).toEqual('/');
44+
expect(joinPaths('a', '/')).toEqual('a/');
45+
expect(joinPaths('', '/', '/')).toEqual('/');
46+
expect(joinPaths('/', '/')).toEqual('/');
47+
expect(joinPaths('/', '')).toEqual('/');
48+
expect(joinPaths('/', 'b')).toEqual('/b');
49+
expect(joinPaths('/', 'b/')).toEqual('/b/');
50+
expect(joinPaths('/', '', '/')).toEqual('/');
51+
expect(joinPaths('/', 'b', '/')).toEqual('/b/');
52+
expect(joinPaths('/', 'b/', '/')).toEqual('/b/');
53+
expect(joinPaths('a', '/', '/')).toEqual('a/');
54+
expect(joinPaths('/', '/', 'c')).toEqual('/c');
55+
expect(joinPaths('/', '/', 'c/')).toEqual('/c/');
56+
});
57+
1858
test('isObject', () => {
1959
expect(isObject({})).toBeTrue();
2060
expect(isObject([])).toBeFalse();

0 commit comments

Comments
 (0)