diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | app/assets/javascripts/main.js | 13 | ||||
| -rw-r--r-- | app/assets/javascripts/sw.js | 32 | ||||
| -rw-r--r-- | app/assets/stylesheets/errors.scss | 3 | ||||
| -rw-r--r-- | app/controllers/application_controller.rb | 4 | ||||
| -rw-r--r-- | app/controllers/pwa_controller.rb | 5 | ||||
| -rw-r--r-- | app/views/pwa/offline.html.haml | 14 | ||||
| -rw-r--r-- | app/views/pwa/service_worker.js.erb | 31 | ||||
| -rw-r--r-- | changelogs/unreleased/pwa.yml | 5 | ||||
| -rw-r--r-- | config/routes.rb | 4 | ||||
| -rw-r--r-- | config/webpack.config.js | 33 | ||||
| -rw-r--r-- | lib/gitlab/path_regex.rb | 1 | ||||
| -rw-r--r-- | lib/gitlab/webpack/dev_server_middleware.rb | 6 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | yarn.lock | 51 |
15 files changed, 153 insertions, 51 deletions
diff --git a/.gitignore b/.gitignore index 0696dd217af..268f5e6fc35 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ eslint-report.html /public/assets/ /public/uploads.* /public/uploads/ +/public/service_worker.js /shared/artifacts/ /spec/javascripts/fixtures/blob/pdf/ /spec/javascripts/fixtures/blob/balsamiq/ diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 701c944a8ab..e7c8b5ae239 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -285,12 +285,11 @@ document.addEventListener('DOMContentLoaded', () => { // initialize field errors $('.gl-show-field-errors').each((i, form) => new GlFieldErrors(form)); + if ('serviceWorker' in navigator && gon.features && gon.features.serviceWorker) { + navigator.serviceWorker.register('/service_worker.js', { + scope: '/', + }); + } + requestIdleCallback(deferredInitialisation); }); - -// Register a service worker if our browser allows it -if (navigator.serviceWorker) { - navigator.serviceWorker.register('/@service_worker.js', { - scope: '/', - }); -} diff --git a/app/assets/javascripts/sw.js b/app/assets/javascripts/sw.js new file mode 100644 index 00000000000..53151561e10 --- /dev/null +++ b/app/assets/javascripts/sw.js @@ -0,0 +1,32 @@ +const CURRENT_CACHE = '<%= Gitlab.version %>_<%= Gitlab.revision %>'; +const OFFLINE_PAGE = '/-/offline'; + +// eslint-disable-next-line no-restricted-globals +self.addEventListener('install', event => { + event.waitUntil(caches.open(CURRENT_CACHE).then(cache => cache.add(OFFLINE_PAGE))); +}); + +// eslint-disable-next-line no-restricted-globals +self.addEventListener('activate', event => { + event.waitUntil( + caches + .keys() + .then(cacheNames => + Promise.all( + cacheNames.map(cache => + cache !== CURRENT_CACHE ? caches.delete(cache) : Promise.resolve(), + ), + ), + ), + ); +}); + +// eslint-disable-next-line no-restricted-globals +self.addEventListener('fetch', event => { + const { request } = event; + const { method, mode } = request; + + if (method === 'GET' && mode === 'navigate') { + event.respondWith(fetch(request).catch(() => caches.match(OFFLINE_PAGE))); + } +}); diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss index 4b82038fd4f..658e0ff638e 100644 --- a/app/assets/stylesheets/errors.scss +++ b/app/assets/stylesheets/errors.scss @@ -41,10 +41,9 @@ h3 { } img { + max-width: 80vw; display: block; margin: 40px auto; - max-width: 80vw; - width: 500px; } a { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b7eb6af6d67..57f2ded0448 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -26,6 +26,10 @@ class ApplicationController < ActionController::Base before_action :set_usage_stats_consent_flag before_action :check_impersonation_availability + before_action do + push_frontend_feature_flag(:service_worker) + end + around_action :set_locale after_action :set_page_title_header, if: :json_request? diff --git a/app/controllers/pwa_controller.rb b/app/controllers/pwa_controller.rb index 3311af499cd..051b9df0803 100644 --- a/app/controllers/pwa_controller.rb +++ b/app/controllers/pwa_controller.rb @@ -9,5 +9,10 @@ class PwaController < ApplicationController end def service_worker + respond_to do |format| + format.js do + render file: "public/assets/webpack/service_worker.js", layout: nil, content_type: 'text/javascript' + end + end end end diff --git a/app/views/pwa/offline.html.haml b/app/views/pwa/offline.html.haml index b62a64ae057..bfec6cb7275 100644 --- a/app/views/pwa/offline.html.haml +++ b/app/views/pwa/offline.html.haml @@ -1,5 +1,17 @@ - content_for(:title, 'Offline') -= image_tag('illustrations/pipelines_failed.svg', alt: 'offline', lazy: false) + +-# This SVG is inlined so we can cache it with the page +%svg{ :viewbox => "0 0 492.50943 453.67966", :xmlns => "http://www.w3.org/2000/svg", :style => "display: block; margin: 40px auto; max-width: 80vw; width: 300px;" } + %g{ :fill => "none", "fill-rule" => "evenodd" } + %path{ :d => "M491.58891 259.39833l-27.55867-84.81467L409.41291 6.48633c-2.80934-8.648-15.04533-8.648-17.856 0l-54.61867 168.09733H155.57158l-54.62-168.09733c-2.80933-8.648-15.04533-8.648-17.856 0L28.47825 174.58366.92092 259.39833c-2.514669 7.736.24 16.21066 6.82 20.992l238.51333 173.28933 238.51466-173.28933c6.58-4.78134 9.33333-13.256 6.82-20.992", :fill => "#fc6d26" } + %path{ :d => "M246.25478 453.67966l90.684-279.096h-181.368z", :fill => "#e24329" } + %path{ :d => "M246.25478 453.67912l-90.684-279.09466h-127.092z", :fill => "#fc6d26" } + %path{ :d => "M28.47878 174.58406L.92012 259.39873c-2.513336 7.736.24 16.21066 6.82133 20.99066l238.51333 173.28933z", :fill => "#fca326" } + %path{ :d => "M28.47878 174.58433h127.092L100.95212 6.487c-2.81067-8.64933-15.04667-8.64933-17.856 0z", :fill => "#e24329" } + %path{ :d => "M246.25478 453.67912l90.684-279.09466h127.09199z", :fill => "#fc6d26" } + %path{ :d => "M464.03064 174.58406l27.55867 84.81467c2.51333 7.736-.24 16.21066-6.82134 20.99066L246.25465 453.67872z", :fill => "#fca326" } + %path{ :d => "M464.03064 174.58433h-127.092L391.55731 6.487c2.81066-8.64933 15.04666-8.64933 17.856 0z", :fill => "#e24329" } + .container %h3 = s_('offline|You don\'t currently have an internet connection') diff --git a/app/views/pwa/service_worker.js.erb b/app/views/pwa/service_worker.js.erb deleted file mode 100644 index b409eafb595..00000000000 --- a/app/views/pwa/service_worker.js.erb +++ /dev/null @@ -1,31 +0,0 @@ -const CURRENT_CACHE = '<%= Gitlab::VERSION %>_<%= Gitlab.revision %>'; - -// eslint-disable-next-line no-restricted-globals -self.addEventListener('install', event => { - event.waitUntil(caches.open(CURRENT_CACHE).then(cache => cache.addAll(['/-/offline']))); -}); - -// eslint-disable-next-line no-restricted-globals -self.addEventListener('activate', event => { - event.waitUntil( - caches.keys().then(cacheNames => { - return Promise.all( - cacheNames.map(cache => - cache !== CURRENT_CACHE ? caches.delete(cache) : Promise.resolve(), - ), - ); - }), - ); -}); - -// eslint-disable-next-line no-restricted-globals -self.addEventListener('fetch', event => { - const { request } = event; - - // We only want to intercept the GET requests for now - if (request.method === 'GET') { - event.respondWith( - fetch(request).catch(() => (request.mode === 'navigate' ? caches.match('/-/offline') : null)), - ); - } -}); diff --git a/changelogs/unreleased/pwa.yml b/changelogs/unreleased/pwa.yml new file mode 100644 index 00000000000..e5d3e086cf6 --- /dev/null +++ b/changelogs/unreleased/pwa.yml @@ -0,0 +1,5 @@ +--- +title: Qualifies GitLab as a Progressive Web App by adding basic offline support +merge_request: 23578 +author: +type: added diff --git a/config/routes.rb b/config/routes.rb index 7a741dd4300..4b00df2e701 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -151,8 +151,6 @@ Rails.application.routes.draw do root to: "root#index" - # Service Worker - get '@service_worker.js', to: 'pwa#service_worker' - + get 'service_worker.js', to: 'pwa#service_worker' get '*unmatched_route', to: 'application#route_not_found' end diff --git a/config/webpack.config.js b/config/webpack.config.js index 20b3f4c0264..dce09c2f3f5 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -7,6 +7,7 @@ const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; const CompressionPlugin = require('compression-webpack-plugin'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const CopyPlugin = require('copy-webpack-plugin'); const ROOT_PATH = path.resolve(__dirname, '..'); const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache'); @@ -106,6 +107,27 @@ if (IS_EE) { }); } +function version(content) { + const VERSION_FILE = path.resolve(__dirname, '../VERSION'); + const REVISION_FILE = path.resolve(__dirname, '../REVISION'); + + // Return the GitLab version from the version file, or unknown + const GITLAB_VERSION = fs.existsSync(VERSION_FILE) + ? fs.readFileSync(VERSION_FILE, 'utf8').trim() + : 'Unknown'; + + // Return the revision from the revision file, or a random string + // This helps bust the cache when changes are made locally + const GITLAB_REVISION = fs.existsSync(REVISION_FILE) + ? fs.readFileSync(REVISION_FILE, 'utf8').trim() + : Math.random().toString(36); + + return content + .toString() + .replace('<%= Gitlab.version %>', GITLAB_VERSION.trim()) + .replace('<%= Gitlab.revision %>', GITLAB_REVISION.trim()); +} + module.exports = { mode: IS_PRODUCTION ? 'production' : 'development', @@ -282,6 +304,17 @@ module.exports = { } }), + // Copies and versions the service workers + new CopyPlugin([ + { + from: path.join(ROOT_PATH, 'app/assets/javascripts/sw.js'), + to: path.join(ROOT_PATH, 'public/assets/webpack/service_worker.js'), + transform(content) { + return version(content); + }, + }, + ]), + // compression can require a lot of compute time and is disabled in CI IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(), diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index 3c888be0710..0d4eebf4a47 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -51,6 +51,7 @@ module Gitlab s search sent_notifications + service_worker.js slash-command-logo.png snippets u diff --git a/lib/gitlab/webpack/dev_server_middleware.rb b/lib/gitlab/webpack/dev_server_middleware.rb index fda41da5a94..758a8af214c 100644 --- a/lib/gitlab/webpack/dev_server_middleware.rb +++ b/lib/gitlab/webpack/dev_server_middleware.rb @@ -16,12 +16,16 @@ module Gitlab end def perform_request(env) - if @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}") + if @proxy_path && env['PATH_INFO'].start_with?("/#{@proxy_path}", "/service_worker.js") if relative_url_root = Rails.application.config.relative_url_root env['SCRIPT_NAME'] = "" env['REQUEST_PATH'].sub!(/\A#{Regexp.escape(relative_url_root)}/, '') end + if env['PATH_INFO'].start_with?("/service_worker.js") + env['PATH_INFO'] = "/assets/webpack/service_worker.js" + end + super(env) else @app.call(env) diff --git a/package.json b/package.json index cb063c9782c..8dadb7fe4a2 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "clipboard": "^1.7.1", "codesandbox-api": "^0.0.20", "compression-webpack-plugin": "^2.0.0", + "copy-webpack-plugin": "^5.0.1", "core-js": "^2.4.1", "cropper": "^2.3.0", "css-loader": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 2bdbca103b1..0241363d34d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1864,7 +1864,7 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -cacache@^11.0.2, cacache@^11.2.0: +cacache@^11.0.2, cacache@^11.2.0, cacache@^11.3.1: version "11.3.2" resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa" integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg== @@ -2542,6 +2542,23 @@ copy-to-clipboard@^3.0.8: dependencies: toggle-selection "^1.0.3" +copy-webpack-plugin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.1.tgz#97997989cc5bc69976bf64f660bd19663481f089" + integrity sha512-yMTURAkYZO/6h6pGMbHQl2jpKtRNC+0Cy/4kRRP6qUHmpbGGAzNnyMecE6aHgGFCb4ksrL3YcDqYGb8ds3J9cw== + dependencies: + cacache "^11.3.1" + find-cache-dir "^2.0.0" + glob-parent "^3.1.0" + globby "^7.1.1" + is-glob "^4.0.0" + loader-utils "^1.1.0" + minimatch "^3.0.4" + normalize-path "^3.0.0" + p-limit "^2.1.0" + serialize-javascript "^1.4.0" + webpack-log "^2.0.0" + core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -3271,7 +3288,7 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^2.2.1: +dir-glob@^2.0.0, dir-glob@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== @@ -4680,6 +4697,18 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" + integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA= + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + globby@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-9.0.0.tgz#3800df736dc711266df39b4ce33fe0d481f94c23" @@ -5117,6 +5146,11 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" +ignore@^3.3.5: + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" + integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + ignore@^4.0.3, ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -7804,10 +7838,10 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" - integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A== +p-limit@^2.0.0, p-limit@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== dependencies: p-try "^2.0.0" @@ -9476,6 +9510,11 @@ sisteransi@^1.0.0: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ== +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= + slash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" |
