From dd8e4632f4c3ccf95b228110b2b5cec7a7455eb9 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sun, 1 Jan 2017 13:12:38 +0000 Subject: Started on service worker, basics setup but still not a working poc --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/cache_worker_client.js.es6 | 50 +++++++++++++++ .../service_workers/cache_worker.js.es6 | 73 ++++++++++++++++++++++ app/controllers/manifest_json_controller.rb | 14 +++++ app/views/layouts/_head.html.haml | 2 + app/views/shared/manifest.json.erb | 20 ++++++ config/application.rb | 2 + config/routes.rb | 2 + lib/gitlab/gon_helper.rb | 5 ++ 9 files changed, 169 insertions(+) create mode 100644 app/assets/javascripts/cache_worker_client.js.es6 create mode 100644 app/assets/javascripts/service_workers/cache_worker.js.es6 create mode 100644 app/controllers/manifest_json_controller.rb create mode 100644 app/views/shared/manifest.json.erb diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e43afbb4cc9..405e7d2a95e 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -61,6 +61,7 @@ /*= require_directory . */ /*= require fuzzaldrin-plus */ /*= require es6-promise.auto */ +/*= require cache_worker_client */ (function () { document.addEventListener('page:fetch', function () { diff --git a/app/assets/javascripts/cache_worker_client.js.es6 b/app/assets/javascripts/cache_worker_client.js.es6 new file mode 100644 index 00000000000..db45b6adf8e --- /dev/null +++ b/app/assets/javascripts/cache_worker_client.js.es6 @@ -0,0 +1,50 @@ +(() => { + const global = window.gl || (window.gl = {}); + + class ServiceWorkerClient { + constructor() { + if (!navigator.serviceWorker) throw new ReferenceError('Your browser does not support service workers'); + + this.worker = navigator.serviceWorker; + this.cacheWorkerPath = gon.cache_worker_path; + this.assetPaths = gon.asset_paths; + } + + install() { + this.worker.register(this.cacheWorkerPath) + .then((registration) => { + console.log('registration successful!', registration); + this.sendMessage(registration.active, { + type: 'add_assets', + data: this.assetPaths, + }); + }) + .catch((err) => { + console.log('registration failed!', err); + }); + } + + sendMessage(client = this.worker.controller, message, transferable = []) { + return new Promise((resolve, reject) => { + const messageChannel = new MessageChannel(); + + messageChannel.port1.onmessage = event => this.receiveReply(event, message, resolve, reject); + + client.postMessage(message, [messageChannel.port2].concat(transferable)); + }); + } + + receiveReply(event, message, resolve, reject) { + console.log('reply received', event, message); + resolve(); + } + } + + global.ServiceWorkerClient = ServiceWorkerClient; + + $(() => { + if (global.serviceWorkerClient) return; + global.serviceWorkerClient = new global.ServiceWorkerClient(); + global.serviceWorkerClient.install(); + }); +})(); diff --git a/app/assets/javascripts/service_workers/cache_worker.js.es6 b/app/assets/javascripts/service_workers/cache_worker.js.es6 new file mode 100644 index 00000000000..c4b21e454ce --- /dev/null +++ b/app/assets/javascripts/service_workers/cache_worker.js.es6 @@ -0,0 +1,73 @@ +var CACHE = 'cache-update-and-refresh'; + +// On install, cache some resource. +self.addEventListener('install', function(evt) { + console.log('The service worker is being installed.'); +}); + +// On fetch, use cache but update the entry with the latest contents +// from the server. +self.addEventListener('fetch', function(evt) { + console.log('The service worker is serving the asset.'); + // You can use `respondWith()` to answer ASAP... + evt.respondWith(fromCache(evt.request)); + // ...and `waitUntil()` to prevent the worker to be killed until + // the cache is updated. + evt.waitUntil( + update(evt.request) + // Finally, send a message to the client to inform it about the + // resource is up to date. + .then(refresh) + ); +}); + +self.addEventListener('message', function(event) { + console.log('message recieved is', event); + if (event.data.type === 'add_assets') { + event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(event.data.data))); + } +}); + +// Open the cache where the assets were stored and search for the requested +// resource. Notice that in case of no matching, the promise still resolves +// but it does with `undefined` as value. +function fromCache(request) { + return caches.open(CACHE).then(function (cache) { + return cache.match(request); + }); +} + + +// Update consists in opening the cache, performing a network request and +// storing the new response data. +function update(request) { + return caches.open(CACHE).then(function (cache) { + return fetch(request).then(function (response) { + return cache.put(request, response.clone()).then(function () { + return response; + }); + }); + }); +} + +// Sends a message to the clients. +function refresh(response) { + if (self.clients.length === 0) return; + return self.clients.matchAll().then(function (clients) { + clients.forEach(function (client) { + // Encode which resource has been updated. By including the + // [ETag](https://en.wikipedia.org/wiki/HTTP_ETag) the client can + // check if the content has changed. + var message = { + type: 'refresh', + url: response.url, + // Notice not all servers return the ETag header. If this is not + // provided you should use other cache headers or rely on your own + // means to check if the content has changed. + eTag: response.headers.get('ETag') + }; + // Tell the client about the update. + client.postMessage(JSON.stringify(message)); + }); + }); +} diff --git a/app/controllers/manifest_json_controller.rb b/app/controllers/manifest_json_controller.rb new file mode 100644 index 00000000000..319a661b3f1 --- /dev/null +++ b/app/controllers/manifest_json_controller.rb @@ -0,0 +1,14 @@ +class ManifestJsonController < ApplicationController + skip_before_action :authenticate_user!, :reject_blocked! + + def index + puts Gitlab.config.gitlab.inspect + render 'shared/manifest.json', locals: { + homepage_url: Gitlab.config.gitlab.url, + icons: { + '32' => ActionController::Base.helpers.asset_path('favicon.ico'), + '128' => ActionController::Base.helpers.asset_path('gitlab_logo.png') + } + } + end +end diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 3096f0ee19e..94f63f1c072 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -47,6 +47,8 @@ = favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152' %link{ rel: 'mask-icon', href: image_path('logo.svg'), color: 'rgb(226, 67, 41)' } + %link{ rel: 'manifest', href: manifest_json_path } + -# Windows 8 pinned site tile %meta{ name: 'msapplication-TileImage', content: image_path('msapplication-tile.png') } %meta{ name: 'msapplication-TileColor', content: '#30353E' } diff --git a/app/views/shared/manifest.json.erb b/app/views/shared/manifest.json.erb new file mode 100644 index 00000000000..364c171fb47 --- /dev/null +++ b/app/views/shared/manifest.json.erb @@ -0,0 +1,20 @@ +{ + "name": "GitLab", + "short_name": "GitLab", + "description": "Next-generation developer collaboration software.", + "start_url": ".", + "lang": "en-US", + "dir": "ltr", + "display": "standalone", + "background_color": "#fff", + "theme_color": "#e24329", + "icons": [{ + "src": "<%=icons['32']%>", + "sizes": "32x32", + "type": "image/x-icon" + }, { + "src": "<%=icons['128']%>", + "sizes": "128x128", + "type": "image/png" + }] +} diff --git a/config/application.rb b/config/application.rb index d36c6d5c92e..f59d42c6b26 100644 --- a/config/application.rb +++ b/config/application.rb @@ -108,6 +108,8 @@ module Gitlab config.assets.precompile << "terminal/terminal_bundle.js" config.assets.precompile << "lib/utils/*.js" config.assets.precompile << "lib/*.js" + config.assets.precompile << "service_workers/*.js" + config.assets.precompile << "manifest.json" config.assets.precompile << "u2f.js" config.assets.precompile << "vendor/assets/fonts/*" diff --git a/config/routes.rb b/config/routes.rb index 06d565df469..aa378154bd1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,6 +43,8 @@ Rails.application.routes.draw do # Koding route get 'koding' => 'koding#index' + get 'manifest.json', to: 'manifest_json#index', as: :manifest_json + draw :api draw :sidekiq draw :help diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 4d4e04e9e35..d30b5d7a4c5 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -10,6 +10,11 @@ module Gitlab gon.award_menu_url = emojis_path gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css') gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js') + gon.cache_worker_path = ActionController::Base.helpers.asset_path('service_workers/cache_worker.js') + gon.asset_paths = [ + ActionController::Base.helpers.asset_path('application.js'), + ActionController::Base.helpers.asset_path('application.css') + ] if current_user gon.current_user_id = current_user.id -- cgit v1.2.3