diff --git a/app/assets/javascripts/discourse/initializers/android-app-banner-service-worker.js.es6 b/app/assets/javascripts/discourse/initializers/android-app-banner-service-worker.js.es6 deleted file mode 100644 index e24cb2d6a2a..00000000000 --- a/app/assets/javascripts/discourse/initializers/android-app-banner-service-worker.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -// Android Chrome App Banner requires at least **one** service worker to be instantiate and https. -// After Discourse starts to use service workers for other stuff (like mobile notification, offline mode, or ember) -// we can ditch this. - -export default { - name: 'android-app-banner-service-worker', - - initialize(container) { - const caps = container.lookup('capabilities:main'); - const isSecure = document.location.protocol === 'https:'; - - if (isSecure && caps.isAndroid && 'serviceWorker' in navigator) { - navigator.serviceWorker.register(Discourse.BaseUri + '/service-worker.js'); - } - } -}; diff --git a/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 b/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 new file mode 100644 index 00000000000..7c16106ddae --- /dev/null +++ b/app/assets/javascripts/discourse/initializers/register-service-worker.js.es6 @@ -0,0 +1,12 @@ +export default { + name: 'register-service-worker', + + initialize() { + const isSecure = (document.location.protocol === 'https:') || + (location.hostname === "localhost"); + + if (isSecure && ('serviceWorker' in navigator)) { + navigator.serviceWorker.register(`${Discourse.BaseUri}/service-worker.js`); + } + } +}; diff --git a/public/service-worker.js b/app/assets/javascripts/service-worker.js.erb similarity index 94% rename from public/service-worker.js rename to app/assets/javascripts/service-worker.js.erb index 50d94544592..138345daa3f 100644 --- a/public/service-worker.js +++ b/app/assets/javascripts/service-worker.js.erb @@ -1,15 +1,13 @@ -/* - I'm here to support Google Chrome App Banner on Android - */ - 'use strict'; // Incrementing CACHE_VERSION will kick off the install event and force previously cached // resources to be cached again. const CACHE_VERSION = 1; -let CURRENT_CACHES = { + +const CURRENT_CACHES = { offline: 'offline-v' + CACHE_VERSION }; + const OFFLINE_URL = 'offline.html'; function createCacheBustedRequest(url) { @@ -23,7 +21,7 @@ function createCacheBustedRequest(url) { // If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead. let bustedUrl = new URL(url, self.location.href); - bustedUrl.search += (bustedUrl.search ? '&' : '') + 'cachebust=' + Date.now(); + bustedUrl.search += `${(bustedUrl.search ? '&' : '')}cachebust=${Date.now()}`; return new Request(bustedUrl); } @@ -88,3 +86,7 @@ self.addEventListener('fetch', event => { // event.respondWith(). If no fetch handlers call event.respondWith(), the request will be // handled by the browser as if there were no service worker involvement. }); + +<% DiscoursePluginRegistry.service_workers.each do |js| %> +<%=raw "#{File.read(js)}" %> +<% end %> diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index 2b32131c9d9..f54691ea934 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -4,7 +4,7 @@ require_dependency 'file_helper' class StaticController < ApplicationController skip_before_action :check_xhr, :redirect_to_login_if_required - skip_before_action :verify_authenticity_token, only: [:brotli_asset, :cdn_asset, :enter, :favicon] + skip_before_action :verify_authenticity_token, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup'] @@ -143,6 +143,14 @@ class StaticController < ApplicationController serve_asset end + def service_worker_asset + respond_to do |format| + format.js do + render plain: Rails.application.assets["service-worker.js"].to_s + end + end + end + protected def serve_asset(suffix = nil) diff --git a/config/routes.rb b/config/routes.rb index 9a4baf6fa13..394ad7d9bde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -687,6 +687,8 @@ Discourse::Application.routes.draw do post "draft" => "draft#update" delete "draft" => "draft#destroy" + get "service-worker" => "static#service_worker_asset", format: :js + get "cdn_asset/:site/*path" => "static#cdn_asset", format: false get "brotli_asset/*path" => "static#brotli_asset", format: false diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb index dfc0e008e38..afe8009f8c5 100644 --- a/lib/discourse_plugin_registry.rb +++ b/lib/discourse_plugin_registry.rb @@ -5,6 +5,7 @@ class DiscoursePluginRegistry class << self attr_writer :javascripts + attr_writer :service_workers attr_writer :admin_javascripts attr_writer :stylesheets attr_writer :mobile_stylesheets @@ -24,6 +25,10 @@ class DiscoursePluginRegistry @javascripts ||= Set.new end + def service_workers + @service_workers ||= Set.new + end + def asset_globs @asset_globs ||= Set.new end @@ -79,6 +84,10 @@ class DiscoursePluginRegistry self.class.javascripts << filename end + def self.register_service_worker(filename, options = {}) + self.service_workers << filename + end + def register_css(filename) self.class.stylesheets << filename end @@ -166,6 +175,10 @@ class DiscoursePluginRegistry self.class.javascripts end + def service_workers + self.class.service_workers + end + def stylesheets self.class.stylesheets end @@ -188,6 +201,7 @@ class DiscoursePluginRegistry def self.clear self.javascripts = nil + self.service_workers = nil self.stylesheets = nil self.mobile_stylesheets = nil self.desktop_stylesheets = nil @@ -197,6 +211,7 @@ class DiscoursePluginRegistry def self.reset! javascripts.clear + service_workers.clear admin_javascripts.clear stylesheets.clear mobile_stylesheets.clear diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index ee399c49217..197606da79b 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -29,6 +29,7 @@ class Plugin::Instance :color_schemes, :initializers, :javascripts, + :service_workers, :styles, :themes].each do |att| class_eval %Q{ @@ -332,6 +333,13 @@ class Plugin::Instance assets << [full_path, opts] end + def register_service_worker(file, opts = nil) + service_workers << [ + File.join(File.dirname(path), 'assets', file), + opts + ] + end + def register_color_scheme(name, colors) color_schemes << { name: name, colors: colors } end @@ -420,6 +428,8 @@ JS register_assets! unless assets.blank? + register_service_workers! + seed_data.each do |key, value| DiscoursePluginRegistry.register_seed_data(key, value) end @@ -516,6 +526,12 @@ JS end end + def register_service_workers! + service_workers.each do |asset, opts| + DiscoursePluginRegistry.register_service_worker(asset, opts) + end + end + private def write_asset(path, contents) diff --git a/spec/components/discourse_plugin_registry_spec.rb b/spec/components/discourse_plugin_registry_spec.rb index 68542ac0eb1..b6b00018e65 100644 --- a/spec/components/discourse_plugin_registry_spec.rb +++ b/spec/components/discourse_plugin_registry_spec.rb @@ -92,6 +92,25 @@ describe DiscoursePluginRegistry do end end + context '.register_service_worker' do + let(:registry) { DiscoursePluginRegistry } + + before do + registry.register_service_worker('hello.js') + end + + after do + registry.reset! + end + + it "should register the file once" do + 2.times { registry.register_service_worker('hello.js') } + + expect(registry.service_workers.size).to eq(1) + expect(registry.service_workers).to include('hello.js') + end + end + context '.register_archetype' do it "delegates archetypes to the Archetype component" do Archetype.expects(:register).with('threaded', hello: 123) diff --git a/spec/components/plugin/instance_spec.rb b/spec/components/plugin/instance_spec.rb index 35a770db519..7c1f37f6a50 100644 --- a/spec/components/plugin/instance_spec.rb +++ b/spec/components/plugin/instance_spec.rb @@ -95,6 +95,18 @@ describe Plugin::Instance do end end + context "register service worker" do + it "populates the DiscoursePluginRegistry" do + plugin = Plugin::Instance.new nil, "/tmp/test.rb" + plugin.register_service_worker("test.js") + plugin.register_service_worker("test2.js") + + plugin.send :register_service_workers! + + expect(DiscoursePluginRegistry.service_workers.count).to eq(2) + end + end + context "activate!" do it "can activate plugins correctly" do plugin = Plugin::Instance.new