Skip to content

Commit 8ab815a

Browse files
authored
Unregister non-matching serviceworkers (#15834)
* 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 b61092b commit 8ab815a

File tree

5 files changed

+80
-30
lines changed

5 files changed

+80
-30
lines changed

models/user.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ var (
789789
"debug",
790790
"error",
791791
"explore",
792+
"favicon.ico",
792793
"ghost",
793794
"help",
794795
"install",
@@ -807,10 +808,10 @@ var (
807808
"repo",
808809
"robots.txt",
809810
"search",
811+
"serviceworker.js",
810812
"stars",
811813
"template",
812814
"user",
813-
"favicon.ico",
814815
}
815816

816817
reservedUserPatterns = []string{"*.keys", "*.gpg"}

web_src/js/features/serviceworker.js

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
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

4-
async function unregister() {
5-
const registrations = await navigator.serviceWorker.getRegistrations();
6-
await Promise.all(registrations.map((registration) => {
7-
return registration.active && registration.unregister();
8-
}));
7+
async function unregisterAll() {
8+
for (const registration of await navigator.serviceWorker.getRegistrations()) {
9+
if (registration.active) await registration.unregister();
10+
}
11+
}
12+
13+
async function unregisterOtherWorkers() {
14+
for (const registration of await navigator.serviceWorker.getRegistrations()) {
15+
const scriptURL = registration.active?.scriptURL || '';
16+
if (!scriptURL.endsWith(workerAssetPath)) await registration.unregister();
17+
}
918
}
1019

1120
async function invalidateCache() {
12-
const cacheKeys = await caches.keys();
13-
await Promise.all(cacheKeys.map((key) => {
14-
return key.startsWith(cachePrefix) && caches.delete(key);
15-
}));
21+
for (const key of await caches.keys()) {
22+
if (key.startsWith(cachePrefix)) caches.delete(key);
23+
}
1624
}
1725

1826
async function checkCacheValidity() {
@@ -30,24 +38,20 @@ export default async function initServiceWorker() {
3038
if (!('serviceWorker' in navigator)) return;
3139

3240
if (UseServiceWorker) {
41+
// unregister all service workers where scriptURL does not match the current one
42+
await unregisterOtherWorkers();
3343
try {
3444
// normally we'd serve the service worker as a static asset from AssetUrlPrefix but
3545
// the spec strictly requires it to be same-origin so it has to be AppSubUrl to work
36-
await Promise.all([
37-
checkCacheValidity(),
38-
navigator.serviceWorker.register(`${AppSubUrl}/assets/serviceworker.js`),
39-
]);
46+
await checkCacheValidity();
47+
await navigator.serviceWorker.register(joinPaths(AppSubUrl, workerAssetPath));
4048
} catch (err) {
4149
console.error(err);
42-
await Promise.all([
43-
invalidateCache(),
44-
unregister(),
45-
]);
50+
await invalidateCache();
51+
await unregisterAll();
4652
}
4753
} else {
48-
await Promise.all([
49-
invalidateCache(),
50-
unregister(),
51-
]);
54+
await invalidateCache();
55+
await unregisterAll();
5256
}
5357
}

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: 40 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,45 @@ 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('a', '/')).toEqual('a/');
44+
expect(joinPaths('', '/', '/')).toEqual('/');
45+
expect(joinPaths('/', '/')).toEqual('/');
46+
expect(joinPaths('/', '')).toEqual('/');
47+
expect(joinPaths('/', 'b')).toEqual('/b');
48+
expect(joinPaths('/', 'b/')).toEqual('/b/');
49+
expect(joinPaths('/', '', '/')).toEqual('/');
50+
expect(joinPaths('/', 'b', '/')).toEqual('/b/');
51+
expect(joinPaths('/', 'b/', '/')).toEqual('/b/');
52+
expect(joinPaths('a', '/', '/')).toEqual('a/');
53+
expect(joinPaths('/', '/', 'c')).toEqual('/c');
54+
expect(joinPaths('/', '/', 'c/')).toEqual('/c/');
55+
});
56+
1857
test('isObject', () => {
1958
expect(isObject({})).toBeTrue();
2059
expect(isObject([])).toBeFalse();

0 commit comments

Comments
 (0)