Feature: Add service worker registration method to plugin API

This commit is contained in:
Jeff Wong 2017-11-22 17:02:01 -08:00 committed by Guo Xiang Tan
parent 46f8a6c97d
commit b094894c94
9 changed files with 93 additions and 23 deletions

View File

@ -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');
}
}
};

View File

@ -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`);
}
}
};

View File

@ -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 %>

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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