Feature: Add service worker registration method to plugin API
This commit is contained in:
parent
46f8a6c97d
commit
b094894c94
|
@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,15 +1,13 @@
|
||||||
/*
|
|
||||||
I'm here to support Google Chrome App Banner on Android
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
|
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
|
||||||
// resources to be cached again.
|
// resources to be cached again.
|
||||||
const CACHE_VERSION = 1;
|
const CACHE_VERSION = 1;
|
||||||
let CURRENT_CACHES = {
|
|
||||||
|
const CURRENT_CACHES = {
|
||||||
offline: 'offline-v' + CACHE_VERSION
|
offline: 'offline-v' + CACHE_VERSION
|
||||||
};
|
};
|
||||||
|
|
||||||
const OFFLINE_URL = 'offline.html';
|
const OFFLINE_URL = 'offline.html';
|
||||||
|
|
||||||
function createCacheBustedRequest(url) {
|
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.
|
// If {cache: 'reload'} didn't have any effect, append a cache-busting URL parameter instead.
|
||||||
let bustedUrl = new URL(url, self.location.href);
|
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);
|
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
|
// 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.
|
// handled by the browser as if there were no service worker involvement.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
<% DiscoursePluginRegistry.service_workers.each do |js| %>
|
||||||
|
<%=raw "#{File.read(js)}" %>
|
||||||
|
<% end %>
|
|
@ -4,7 +4,7 @@ require_dependency 'file_helper'
|
||||||
class StaticController < ApplicationController
|
class StaticController < ApplicationController
|
||||||
|
|
||||||
skip_before_action :check_xhr, :redirect_to_login_if_required
|
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']
|
PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup']
|
||||||
|
|
||||||
|
@ -143,6 +143,14 @@ class StaticController < ApplicationController
|
||||||
serve_asset
|
serve_asset
|
||||||
end
|
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
|
protected
|
||||||
|
|
||||||
def serve_asset(suffix = nil)
|
def serve_asset(suffix = nil)
|
||||||
|
|
|
@ -687,6 +687,8 @@ Discourse::Application.routes.draw do
|
||||||
post "draft" => "draft#update"
|
post "draft" => "draft#update"
|
||||||
delete "draft" => "draft#destroy"
|
delete "draft" => "draft#destroy"
|
||||||
|
|
||||||
|
get "service-worker" => "static#service_worker_asset", format: :js
|
||||||
|
|
||||||
get "cdn_asset/:site/*path" => "static#cdn_asset", format: false
|
get "cdn_asset/:site/*path" => "static#cdn_asset", format: false
|
||||||
get "brotli_asset/*path" => "static#brotli_asset", format: false
|
get "brotli_asset/*path" => "static#brotli_asset", format: false
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ class DiscoursePluginRegistry
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
attr_writer :javascripts
|
attr_writer :javascripts
|
||||||
|
attr_writer :service_workers
|
||||||
attr_writer :admin_javascripts
|
attr_writer :admin_javascripts
|
||||||
attr_writer :stylesheets
|
attr_writer :stylesheets
|
||||||
attr_writer :mobile_stylesheets
|
attr_writer :mobile_stylesheets
|
||||||
|
@ -24,6 +25,10 @@ class DiscoursePluginRegistry
|
||||||
@javascripts ||= Set.new
|
@javascripts ||= Set.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def service_workers
|
||||||
|
@service_workers ||= Set.new
|
||||||
|
end
|
||||||
|
|
||||||
def asset_globs
|
def asset_globs
|
||||||
@asset_globs ||= Set.new
|
@asset_globs ||= Set.new
|
||||||
end
|
end
|
||||||
|
@ -79,6 +84,10 @@ class DiscoursePluginRegistry
|
||||||
self.class.javascripts << filename
|
self.class.javascripts << filename
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.register_service_worker(filename, options = {})
|
||||||
|
self.service_workers << filename
|
||||||
|
end
|
||||||
|
|
||||||
def register_css(filename)
|
def register_css(filename)
|
||||||
self.class.stylesheets << filename
|
self.class.stylesheets << filename
|
||||||
end
|
end
|
||||||
|
@ -166,6 +175,10 @@ class DiscoursePluginRegistry
|
||||||
self.class.javascripts
|
self.class.javascripts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def service_workers
|
||||||
|
self.class.service_workers
|
||||||
|
end
|
||||||
|
|
||||||
def stylesheets
|
def stylesheets
|
||||||
self.class.stylesheets
|
self.class.stylesheets
|
||||||
end
|
end
|
||||||
|
@ -188,6 +201,7 @@ class DiscoursePluginRegistry
|
||||||
|
|
||||||
def self.clear
|
def self.clear
|
||||||
self.javascripts = nil
|
self.javascripts = nil
|
||||||
|
self.service_workers = nil
|
||||||
self.stylesheets = nil
|
self.stylesheets = nil
|
||||||
self.mobile_stylesheets = nil
|
self.mobile_stylesheets = nil
|
||||||
self.desktop_stylesheets = nil
|
self.desktop_stylesheets = nil
|
||||||
|
@ -197,6 +211,7 @@ class DiscoursePluginRegistry
|
||||||
|
|
||||||
def self.reset!
|
def self.reset!
|
||||||
javascripts.clear
|
javascripts.clear
|
||||||
|
service_workers.clear
|
||||||
admin_javascripts.clear
|
admin_javascripts.clear
|
||||||
stylesheets.clear
|
stylesheets.clear
|
||||||
mobile_stylesheets.clear
|
mobile_stylesheets.clear
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Plugin::Instance
|
||||||
:color_schemes,
|
:color_schemes,
|
||||||
:initializers,
|
:initializers,
|
||||||
:javascripts,
|
:javascripts,
|
||||||
|
:service_workers,
|
||||||
:styles,
|
:styles,
|
||||||
:themes].each do |att|
|
:themes].each do |att|
|
||||||
class_eval %Q{
|
class_eval %Q{
|
||||||
|
@ -332,6 +333,13 @@ class Plugin::Instance
|
||||||
assets << [full_path, opts]
|
assets << [full_path, opts]
|
||||||
end
|
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)
|
def register_color_scheme(name, colors)
|
||||||
color_schemes << { name: name, colors: colors }
|
color_schemes << { name: name, colors: colors }
|
||||||
end
|
end
|
||||||
|
@ -420,6 +428,8 @@ JS
|
||||||
|
|
||||||
register_assets! unless assets.blank?
|
register_assets! unless assets.blank?
|
||||||
|
|
||||||
|
register_service_workers!
|
||||||
|
|
||||||
seed_data.each do |key, value|
|
seed_data.each do |key, value|
|
||||||
DiscoursePluginRegistry.register_seed_data(key, value)
|
DiscoursePluginRegistry.register_seed_data(key, value)
|
||||||
end
|
end
|
||||||
|
@ -516,6 +526,12 @@ JS
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def register_service_workers!
|
||||||
|
service_workers.each do |asset, opts|
|
||||||
|
DiscoursePluginRegistry.register_service_worker(asset, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def write_asset(path, contents)
|
def write_asset(path, contents)
|
||||||
|
|
|
@ -92,6 +92,25 @@ describe DiscoursePluginRegistry do
|
||||||
end
|
end
|
||||||
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
|
context '.register_archetype' do
|
||||||
it "delegates archetypes to the Archetype component" do
|
it "delegates archetypes to the Archetype component" do
|
||||||
Archetype.expects(:register).with('threaded', hello: 123)
|
Archetype.expects(:register).with('threaded', hello: 123)
|
||||||
|
|
|
@ -95,6 +95,18 @@ describe Plugin::Instance do
|
||||||
end
|
end
|
||||||
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
|
context "activate!" do
|
||||||
it "can activate plugins correctly" do
|
it "can activate plugins correctly" do
|
||||||
plugin = Plugin::Instance.new
|
plugin = Plugin::Instance.new
|
||||||
|
|
Loading…
Reference in New Issue