From 99d9b0f728536f682b458824371c60b9af1a4edd Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 21 May 2020 21:15:19 +0200 Subject: [PATCH] Move serviceworker to workbox and fix SSE interference Instead of statically hardcoding every frontend asset, this uses a type-based approach to cache all js,css and manifest.json requests. This also fixes the issue that the service worker was interfering with EventSource because it was unconditionally handling all requests which this new implementation doesn't. Fixes: https://github.com/go-gitea/gitea/issues/11092 Fixes: https://github.com/go-gitea/gitea/issues/7372 --- package-lock.json | 22 ++++++++ package.json | 2 + routers/routes/routes.go | 4 -- templates/base/head.tmpl | 25 +-------- templates/pwa/serviceworker_js.tmpl | 83 ---------------------------- web_src/js/features/serviceworker.js | 24 ++++++++ web_src/js/index.js | 2 + web_src/js/serviceworker.js | 14 +++++ webpack.config.js | 9 ++- 9 files changed, 73 insertions(+), 112 deletions(-) delete mode 100644 templates/pwa/serviceworker_js.tmpl create mode 100644 web_src/js/features/serviceworker.js create mode 100644 web_src/js/serviceworker.js diff --git a/package-lock.json b/package-lock.json index 89c41721c73..6206025e084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14857,6 +14857,28 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" }, + "workbox-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-5.1.3.tgz", + "integrity": "sha512-TFSIPxxciX9sFaj0FDiohBeIKpwMcCyNduydi9i3LChItcndDS6TJpErxybv8aBWeCMraXt33TWtF6kKuIObNw==" + }, + "workbox-routing": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-5.1.3.tgz", + "integrity": "sha512-F+sAp9Iy3lVl3BEG+pzXWVq4AftzjiFpHDaZ4Kf4vLoBoKQE0hIHet4zE5DpHqYdyw+Udhp4wrfHamX6PN6z1Q==", + "requires": { + "workbox-core": "^5.1.3" + } + }, + "workbox-strategies": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-5.1.3.tgz", + "integrity": "sha512-wiXHfmOKnWABeIVW+/ye0e00+2CcS5y7SIj2f9zcdy2ZLEbcOf7B+yOl5OrWpBGlTUwRjIYhV++ZqiKm3Dc+8w==", + "requires": { + "workbox-core": "^5.1.3", + "workbox-routing": "^5.1.3" + } + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", diff --git a/package.json b/package.json index 1e81ea1448d..e818e7d7c37 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,8 @@ "webpack": "4.43.0", "webpack-cli": "3.3.11", "webpack-fix-style-only-entries": "0.4.0", + "workbox-routing": "5.1.3", + "workbox-strategies": "5.1.3", "worker-loader": "2.0.0" }, "devDependencies": { diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 7f409eb576f..d739f0b6ca5 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -1048,10 +1048,6 @@ func RegisterRoutes(m *macaron.Macaron) { ctx.HTML(200, "pwa/manifest_json") }) - m.Get("/serviceworker.js", templates.JSRenderer(), func(ctx *context.Context) { - ctx.HTML(200, "pwa/serviceworker_js") - }) - // prometheus metrics endpoint if setting.Metrics.Enabled { c := metrics.NewCollector() diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 8e58c07d23f..2b2791f8291 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -6,30 +6,6 @@ {{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} - {{if UseServiceWorker}} - - {{else}} - - {{end}} @@ -86,6 +62,7 @@ window.config = { AppSubUrl: '{{AppSubUrl}}', StaticUrlPrefix: '{{StaticUrlPrefix}}', + UseServiceWorker: {{UseServiceWorker}}, csrf: '{{.CsrfToken}}', HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}}, Minicolors: {{if .RequireMinicolors}}true{{else}}false{{end}}, diff --git a/templates/pwa/serviceworker_js.tmpl b/templates/pwa/serviceworker_js.tmpl deleted file mode 100644 index a074879f3e0..00000000000 --- a/templates/pwa/serviceworker_js.tmpl +++ /dev/null @@ -1,83 +0,0 @@ -var STATIC_CACHE = 'static-cache-v1'; -var urlsToCache = [ - // js - '{{StaticUrlPrefix}}/fomantic/semantic.min.js?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/js/clipboard.js', - '{{StaticUrlPrefix}}/js/gitgraph.js', - '{{StaticUrlPrefix}}/js/highlight.js', - '{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/js/jquery.js?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/js/swagger.js?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/js/dropzone.js', - '{{StaticUrlPrefix}}/js/datetimepicker.js', - '{{StaticUrlPrefix}}/js/tribute.js', - '{{StaticUrlPrefix}}/vendor/plugins/codemirror/addon/mode/loadmode.js', - '{{StaticUrlPrefix}}/vendor/plugins/codemirror/mode/meta.js', - '{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.min.js', - '{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.js', - - // css - '{{StaticUrlPrefix}}/css/index.css?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/css/swagger.css?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/css/dropzone.css', - '{{StaticUrlPrefix}}/css/datetimepicker.css', - '{{StaticUrlPrefix}}/fomantic/semantic.min.css?v={{MD5 AppVer}}', - '{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css', - '{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css', - '{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css', -{{if .IsSigned }} - {{ if ne .SignedUser.Theme "gitea" }} - '{{StaticUrlPrefix}}/css/theme-{{.SignedUser.Theme}}.css?v={{MD5 AppVer}}', - {{end}} -{{else if ne DefaultTheme "gitea"}} - '{{StaticUrlPrefix}}/css/theme-{{DefaultTheme}}.css?v={{MD5 AppVer}}', -{{end}} - - // img - '{{StaticUrlPrefix}}/img/gitea-sm.png', - '{{StaticUrlPrefix}}/img/gitea-lg.png', - - // svg - '{{StaticUrlPrefix}}/img/svg/icons.svg', - - // fonts - '{{StaticUrlPrefix}}/fomantic/themes/default/assets/fonts/icons.woff2', - '{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-regular.woff2', - '{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-italic.woff2', - '{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-700.woff2', - '{{StaticUrlPrefix}}/vendor/assets/roboto-fonts/roboto-v20-latin-ext_cyrillic-ext_latin_greek_vietnamese_cyrillic_greek-ext-700italic.woff2', - - // monaco - '{{StaticUrlPrefix}}/css/monaco.css', - '{{StaticUrlPrefix}}/fonts/codicon.ttf', - '{{StaticUrlPrefix}}/js/monaco-css.worker.js', - '{{StaticUrlPrefix}}/js/monaco-editor.worker.js', - '{{StaticUrlPrefix}}/js/monaco-html.worker.js', - '{{StaticUrlPrefix}}/js/monaco-json.worker.js', - '{{StaticUrlPrefix}}/js/monaco.js', - '{{StaticUrlPrefix}}/js/monaco-ts.worker.js' -]; - -self.addEventListener('install', function (event) { - // Perform install steps - event.waitUntil( - caches.open(STATIC_CACHE) - .then(function (cache) { - return cache.addAll(urlsToCache); - }) - ); -}); - -self.addEventListener('fetch', function (event) { - event.respondWith( - caches.match(event.request) - .then(function (response) { - // Cache hit - return response - if (response) { - return response; - } - return fetch(event.request); - } - ) - ); -}); diff --git a/web_src/js/features/serviceworker.js b/web_src/js/features/serviceworker.js new file mode 100644 index 00000000000..aa25870538c --- /dev/null +++ b/web_src/js/features/serviceworker.js @@ -0,0 +1,24 @@ +const {UseServiceWorker, AppSubUrl} = window.config; + +async function unregister() { + for (const registration of await navigator.serviceWorker.getRegistrations()) { + const serviceWorker = registration.active; + if (!serviceWorker) continue; + registration.unregister(); + } +} + +export default async function initServiceWorker() { + if (!('serviceWorker' in navigator)) return; + + if (UseServiceWorker) { + try { + navigator.serviceWorker.register(`${AppSubUrl}/serviceworker.js`); + } catch (err) { + console.error(err); + await unregister(); + } + } else { + await unregister(); + } +} diff --git a/web_src/js/index.js b/web_src/js/index.js index fdc5a926dbd..84e08c1dd3d 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -14,6 +14,7 @@ import initGitGraph from './features/gitgraph.js'; import initClipboard from './features/clipboard.js'; import initUserHeatmap from './features/userheatmap.js'; import initDateTimePicker from './features/datetimepicker.js'; +import initServiceWorker from './features/serviceworker.js'; import attachTribute from './features/tribute.js'; import createDropzone from './features/dropzone.js'; import highlight from './features/highlight.js'; @@ -2475,6 +2476,7 @@ $(document).ready(async () => { initGitGraph(), initClipboard(), initUserHeatmap(), + initServiceWorker(), ]); }); diff --git a/web_src/js/serviceworker.js b/web_src/js/serviceworker.js new file mode 100644 index 00000000000..1b009055825 --- /dev/null +++ b/web_src/js/serviceworker.js @@ -0,0 +1,14 @@ +import {registerRoute} from 'workbox-routing'; +import {StaleWhileRevalidate} from 'workbox-strategies'; + +const cachedDestinations = new Set([ + 'manifest', + 'script', + 'style', + 'worker', +]); + +registerRoute( + ({request}) => cachedDestinations.has(request.destination), + new StaleWhileRevalidate({cacheName: 'static-cache-v2'}), +); diff --git a/webpack.config.js b/webpack.config.js index d6a632ad1f9..766fa76ae4f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,13 +35,20 @@ module.exports = { jquery: [ resolve(__dirname, 'web_src/js/jquery.js'), ], + serviceworker: [ + resolve(__dirname, 'web_src/js/serviceworker.js'), + ], icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), ...themes, }, devtool: false, output: { path: resolve(__dirname, 'public'), - filename: 'js/[name].js', + filename: ({chunk}) => { + // serviceworker can only manage assets below it's script path so we + // have to put it in / instead of /js/ + return chunk.id === 'serviceworker' ? '[name].js' : 'js/[name].js'; + }, chunkFilename: 'js/[name].js', }, optimization: {