[devdocsgjs/main: 640/1867] Replace AppCache with a service worker
- From: Andy Holmes <andyholmes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [devdocsgjs/main: 640/1867] Replace AppCache with a service worker
- Date: Fri, 19 Nov 2021 23:47:21 +0000 (UTC)
commit 8ed1f4ace1136c9ee4f45c9063d0980fefe18ede
Author: Jasper van Merle <jaspervmerle gmail com>
Date: Sun Jul 7 00:55:58 2019 +0200
Replace AppCache with a service worker
README.md | 2 +-
assets/javascripts/app/app.coffee | 12 ++--
assets/javascripts/app/appcache.coffee | 42 --------------
assets/javascripts/app/config.coffee.erb | 1 +
assets/javascripts/app/serviceworker.coffee | 55 +++++++++++++++++++
assets/javascripts/app/update_checker.coffee | 6 +-
.../templates/pages/offline_tmpl.coffee | 6 +-
assets/javascripts/views/content/entry_page.coffee | 2 +-
.../javascripts/views/content/settings_page.coffee | 2 -
assets/javascripts/views/layout/resizer.coffee | 4 +-
assets/javascripts/views/layout/settings.coffee | 10 +---
lib/app.rb | 10 ++--
test/app_test.rb | 52 ------------------
views/index.erb | 2 +-
views/manifest.erb | 14 -----
views/service-worker.js.erb | 64 ++++++++++++++++++++++
views/unsupported.erb | 4 +-
17 files changed, 144 insertions(+), 144 deletions(-)
---
diff --git a/README.md b/README.md
index 3c652e9f..d8d92143 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ Contributions are welcome. Please read the [contributing guidelines](./.github/C
* [Doc Browser](https://github.com/qwfy/doc-browser) is a native Linux app that supports DevDocs docsets
* [GNOME Application](https://github.com/hardpixel/devdocs-desktop) GTK3 application with search integrated
in headerbar
* [macOS Application](https://github.com/dteoh/devdocs-macos)
-* [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced
WebView with AppCache enabled
+* [Android Application](https://github.com/Merith-TK/devdocs_webapp_kotlin) is a fully working, advanced
WebView
## Copyright / License
diff --git a/assets/javascripts/app/app.coffee b/assets/javascripts/app/app.coffee
index c638e179..0d2d9814 100644
--- a/assets/javascripts/app/app.coffee
+++ b/assets/javascripts/app/app.coffee
@@ -13,7 +13,7 @@
@el = $('._app')
@localStorage = new LocalStorageStore
- @appCache = new app.AppCache if app.AppCache.isEnabled()
+ @serviceWorker = new app.ServiceWorker if app.ServiceWorker.isEnabled()
@settings = new app.Settings
@db = new app.DB()
@@ -149,7 +149,7 @@
saveDocs: ->
@settings.setDocs(doc.slug for doc in @docs.all())
@db.migrate()
- @appCache?.updateInBackground()
+ @serviceWorker?.updateInBackground()
welcomeBack: ->
visitCount = @settings.get('count')
@@ -169,14 +169,14 @@
reload: ->
@docs.clearCache()
@disabledDocs.clearCache()
- if @appCache then @appCache.reload() else @reboot()
+ if @serviceWorker then @serviceWorker.reload() else @reboot()
return
reset: ->
@localStorage.reset()
@settings.reset()
@db?.reset()
- @appCache?.update()
+ @serviceWorker?.update()
window.location = '/'
return
@@ -195,9 +195,9 @@
return
indexHost: ->
- # Can't load the index files from the host/CDN when applicationCache is
+ # Can't load the index files from the host/CDN when service worker is
# enabled because it doesn't support caching URLs that use CORS.
- @config[if @appCache and @settings.hasDocs() then 'index_path' else 'docs_origin']
+ @config[if @serviceWorker and @settings.hasDocs() then 'index_path' else 'docs_origin']
onBootError: (args...) ->
@trigger 'bootError'
diff --git a/assets/javascripts/app/config.coffee.erb b/assets/javascripts/app/config.coffee.erb
index ec26b697..765da0b5 100644
--- a/assets/javascripts/app/config.coffee.erb
+++ b/assets/javascripts/app/config.coffee.erb
@@ -13,3 +13,4 @@ app.config =
version: <%= Time.now.to_i %>
release: <%= Time.now.utc.httpdate.to_json %>
mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'
+ service_worker_path: '/service-worker.js'
diff --git a/assets/javascripts/app/serviceworker.coffee b/assets/javascripts/app/serviceworker.coffee
new file mode 100644
index 00000000..2faab8f2
--- /dev/null
+++ b/assets/javascripts/app/serviceworker.coffee
@@ -0,0 +1,55 @@
+class app.ServiceWorker
+ $.extend @prototype, Events
+
+ @isEnabled: ->
+ !!navigator.serviceWorker
+
+ constructor: ->
+ @registration = null
+ @installingRegistration = null
+ @notifyUpdate = true
+
+ navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'})
+ .then((registration) => @updateRegistration(registration))
+ .catch((error) -> console.error 'Could not register service worker:', error)
+
+ update: ->
+ return unless @registration
+ @notifyUpdate = true
+ return @doUpdate()
+
+ updateInBackground: ->
+ return unless @registration
+ @notifyUpdate = false
+ return @doUpdate()
+
+ reload: ->
+ return @updateInBackground().then(() -> app.reboot())
+
+ doUpdate: ->
+ return @registration.update().catch(->)
+
+ updateRegistration: (registration) ->
+ $.off @registration, 'updatefound', @onUpdateFound if @registration
+ $.off @installingRegistration, 'statechange', @onStateChange if @installingRegistration
+
+ @registration = registration
+ @installingRegistration = null
+
+ $.on @registration, 'updatefound', @onUpdateFound
+ return
+
+ onUpdateFound: () =>
+ @installingRegistration = @registration.installing
+ $.on @installingRegistration, 'statechange', @onStateChange
+ return
+
+ onStateChange: () =>
+ if @installingRegistration.state == 'installed' and navigator.serviceWorker.controller
+ @updateRegistration(@installingRegistration)
+ @onUpdateReady()
+ return
+
+ onUpdateReady: ->
+ @trigger 'updateready' if @notifyUpdate
+ return
diff --git a/assets/javascripts/app/update_checker.coffee b/assets/javascripts/app/update_checker.coffee
index 5630b488..b98c6563 100644
--- a/assets/javascripts/app/update_checker.coffee
+++ b/assets/javascripts/app/update_checker.coffee
@@ -3,13 +3,13 @@ class app.UpdateChecker
@lastCheck = Date.now()
$.on window, 'focus', @onFocus
- app.appCache.on 'updateready', @onUpdateReady if app.appCache
+ app.serviceWorker.on 'updateready', @onUpdateReady if app.serviceWorker
setTimeout @checkDocs, 0
check: ->
- if app.appCache
- app.appCache.update()
+ if app.serviceWorker
+ app.serviceWorker.update()
else
ajax
url: $('script[src*="application"]').getAttribute('src')
diff --git a/assets/javascripts/templates/pages/offline_tmpl.coffee
b/assets/javascripts/templates/pages/offline_tmpl.coffee
index a9a3c21c..52705605 100644
--- a/assets/javascripts/templates/pages/offline_tmpl.coffee
+++ b/assets/javascripts/templates/pages/offline_tmpl.coffee
@@ -26,7 +26,7 @@ app.templates.offlinePage = (docs) -> """
<dl>
<dt>How does this work?
<dd>Each page is cached as a key-value pair in <a
href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> (downloaded from a single
file).<br>
- The app also uses <a
href="https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache">AppCache</a> and <a
href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a> to cache the assets
and index files.
+ The app also uses <a
href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers">Service
Workers</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">localStorage</a>
to cache the assets and index files.
<dt>Can I close the tab/browser?
<dd>#{canICloseTheTab()}
<dt>What if I don't update a documentation?
@@ -41,10 +41,10 @@ app.templates.offlinePage = (docs) -> """
"""
canICloseTheTab = ->
- if app.AppCache.isEnabled()
+ if app.ServiceWorker.isEnabled()
""" Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and
everything will work as if you were online (provided you installed all the documentations you want to use
beforehand). """
else
- """ No. AppCache isn't available in your browser (or is disabled), so loading <a
href="//devdocs.io">devdocs.io</a> offline won't work.<br>
+ """ No. Service Workers aren't available in your browser (or are disabled), so loading <a
href="//devdocs.io">devdocs.io</a> offline won't work.<br>
The current tab will continue to function even when you go offline (provided you installed all the
documentations beforehand). """
app.templates.offlineDoc = (doc, status) ->
diff --git a/assets/javascripts/views/content/entry_page.coffee
b/assets/javascripts/views/content/entry_page.coffee
index beae4d77..d11291a3 100644
--- a/assets/javascripts/views/content/entry_page.coffee
+++ b/assets/javascripts/views/content/entry_page.coffee
@@ -123,7 +123,7 @@ class app.views.EntryPage extends app.View
@render @tmpl('pageLoadError')
@resetClass()
@addClass @constructor.errorClass
- app.appCache?.update()
+ app.serviceWorker?.update()
return
cache: ->
diff --git a/assets/javascripts/views/content/settings_page.coffee
b/assets/javascripts/views/content/settings_page.coffee
index c1027e9c..af2e9a9d 100644
--- a/assets/javascripts/views/content/settings_page.coffee
+++ b/assets/javascripts/views/content/settings_page.coffee
@@ -22,12 +22,10 @@ class app.views.SettingsPage extends app.View
toggleDark: (enable) ->
app.settings.set('dark', !!enable)
- app.appCache?.updateInBackground()
return
toggleLayout: (layout, enable) ->
app.settings.setLayout(layout, enable)
- app.appCache?.updateInBackground()
return
toggleSmoothScroll: (enable) ->
diff --git a/assets/javascripts/views/layout/resizer.coffee b/assets/javascripts/views/layout/resizer.coffee
index 86bb46f5..8f0ce9c4 100644
--- a/assets/javascripts/views/layout/resizer.coffee
+++ b/assets/javascripts/views/layout/resizer.coffee
@@ -26,9 +26,7 @@ class app.views.Resizer extends app.View
newSize = "#{value}px"
@style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize)
@size = newSize
- if save
- app.settings.setSize(value)
- app.appCache?.updateInBackground()
+ app.settings.setSize(value) if save
return
onDragStart: (event) =>
diff --git a/assets/javascripts/views/layout/settings.coffee b/assets/javascripts/views/layout/settings.coffee
index 7888118a..6941b9cd 100644
--- a/assets/javascripts/views/layout/settings.coffee
+++ b/assets/javascripts/views/layout/settings.coffee
@@ -25,7 +25,6 @@ class app.views.Settings extends app.View
if super
@render()
document.body.classList.remove(SIDEBAR_HIDDEN_LAYOUT)
- app.appCache?.on 'progress', @onAppCacheProgress
return
deactivate: ->
@@ -33,7 +32,6 @@ class app.views.Settings extends app.View
@resetClass()
@docPicker.detach()
document.body.classList.add(SIDEBAR_HIDDEN_LAYOUT) if app.settings.hasLayout(SIDEBAR_HIDDEN_LAYOUT)
- app.appCache?.off 'progress', @onAppCacheProgress
return
render: ->
@@ -52,7 +50,7 @@ class app.views.Settings extends app.View
docs = @docPicker.getSelectedDocs()
app.settings.setDocs(docs)
- @saveBtn.textContent = if app.appCache then 'Downloading\u2026' else 'Saving\u2026'
+ @saveBtn.textContent = 'Saving\u2026'
disabledDocs = new app.collections.Docs(doc for doc in app.docs.all() when docs.indexOf(doc.slug) is
-1)
disabledDocs.uninstall ->
app.db.migrate()
@@ -83,9 +81,3 @@ class app.views.Settings extends app.View
$.stopEvent(event)
app.router.show '/'
return
-
- onAppCacheProgress: (event) =>
- if event.lengthComputable
- percentage = Math.round event.loaded * 100 / event.total
- @saveBtn.textContent = "Downloading\u2026 (#{percentage}%)"
- return
diff --git a/lib/app.rb b/lib/app.rb
index 32cac31b..18dbe901 100644
--- a/lib/app.rb
+++ b/lib/app.rb
@@ -220,7 +220,7 @@ class App < Sinatra::Application
app_theme == 'dark'
end
- def redirect_via_js(path) # courtesy of HTML5 App Cache
+ def redirect_via_js(path)
response.set_cookie :initial_path, value: path, expires: Time.now + 15, path: '/'
redirect '/', 302
end
@@ -243,15 +243,15 @@ class App < Sinatra::Application
end
end
- get '/manifest.appcache' do
- content_type 'text/cache-manifest'
+ get '/service-worker.js' do
+ content_type 'application/javascript'
expires 0, :'no-cache'
- erb :manifest
+ erb :'service-worker.js'
end
get '/' do
return redirect "/#q=#{params[:q]}" if params[:q]
- return redirect '/' unless request.query_string.empty? # courtesy of HTML5 App Cache
+ return redirect '/' unless request.query_string.empty?
response.headers['Content-Security-Policy'] = settings.csp if settings.csp
erb :index
end
diff --git a/test/app_test.rb b/test/app_test.rb
index 92a24acd..909eb42c 100644
--- a/test/app_test.rb
+++ b/test/app_test.rb
@@ -106,58 +106,6 @@ class AppTest < MiniTest::Spec
end
end
- describe "/manifest.appcache" do
- it "works" do
- get '/manifest.appcache'
- assert last_response.ok?
- end
-
- it "works with cookie" do
- set_cookie('docs=css/html~5')
- get '/manifest.appcache'
- assert last_response.ok?
- assert_includes last_response.body, '/css/index.json?1420139788'
- assert_includes last_response.body, '/html~5/index.json?1420139791'
- end
-
- it "ignores invalid docs in the cookie" do
- set_cookie('docs=foo')
- get '/manifest.appcache'
- assert last_response.ok?
- refute_includes last_response.body, 'foo'
- end
-
- it "has the word 'default' when no 'dark' cookie is set" do
- get '/manifest.appcache'
- assert_includes last_response.body, '# default'
- refute_includes last_response.body, '# dark'
- end
-
- it "has the word 'dark' when the cookie is set" do
- set_cookie('dark=1')
- get '/manifest.appcache'
- assert_includes last_response.body, '# dark'
- refute_includes last_response.body, '# default'
- end
-
- it "sets default size" do
- get '/manifest.appcache'
- assert_includes last_response.body, '20rem'
- end
-
- it "sets size from cookie" do
- set_cookie('size=42')
- get '/manifest.appcache'
- assert_includes last_response.body, '42px'
- end
-
- it "sets layout from cookie" do
- set_cookie('layout=foo_layout')
- get '/manifest.appcache'
- assert_includes last_response.body, 'foo_layout'
- end
- end
-
describe "/[doc]" do
it "renders when the doc exists and isn't enabled" do
set_cookie('docs=html~5')
diff --git a/views/index.erb b/views/index.erb
index 022e927f..8e42c3c7 100644
--- a/views/index.erb
+++ b/views/index.erb
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<html<%= ' manifest="/manifest.appcache"' if App.production? %> prefix="og: http://ogp.me/ns#" lang="en"
class="_booting _theme-<%= app_theme %>">
+<html prefix="og: http://ogp.me/ns#" lang="en" class="_booting _theme-<%= app_theme %>">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no">
diff --git a/views/service-worker.js.erb b/views/service-worker.js.erb
new file mode 100644
index 00000000..74ce45b2
--- /dev/null
+++ b/views/service-worker.js.erb
@@ -0,0 +1,64 @@
+<%# Use the hash of the application.js file as cache name, or 'app' if not running in production %>
+<%# This ensures that the cache is always updated if the hash of the application.js file changes %>
+const cacheName = '<%= javascript_path('application', asset_host:
false).scan(/application-([^\.]+)\.js/).last&.first || 'app' %>';
+
+<%# Paths to cache when the service worker is installed %>
+const cachePaths = [
+ '/',
+ '/favicon.ico',
+ '/manifest.json',
+ '/images/webapp-icon-32.png',
+ '/images/webapp-icon-60.png',
+ '/images/webapp-icon-80.png',
+ '/images/webapp-icon-128.png',
+ '/images/webapp-icon-256.png',
+ '/images/webapp-icon-512.png',
+ '<%= manifest_asset_urls.join "',\n '" %>',
+ '<%= doc_index_urls.join "',\n '" %>',
+];
+
+<%# Set-up the cache %>
+self.addEventListener('install', event => {
+ self.skipWaiting();
+
+ event.waitUntil(
+ caches.open(cacheName).then(cache => cache.addAll(cachePaths)),
+ );
+});
+
+<%# Remove old caches %>
+self.addEventListener('activate', event => {
+ event.waitUntil(
+ caches.keys().then(keys => Promise.all(
+ keys.map(key => {
+ if (key !== cacheName) {
+ return caches.delete(key);
+ }
+ })
+ ))
+ );
+});
+
+<%# Handle HTTP requests %>
+self.addEventListener('fetch', event => {
+ event.respondWith(
+ caches.match(event.request).then(response => {
+ if (response) {
+ return response;
+ }
+
+ return fetch(event.request)
+ .catch(err => {
+ const url = new URL(event.request.url);
+
+ <%# Return the index page from the cache if the user is visiting a url like
devdocs.io/javascript/global_objects/array/find %>
+ <%# The index page will make sure the correct documentation or a proper offline page is shown %>
+ if (url.origin === location.origin && !url.pathname.includes('.')) {
+ return caches.match('/').then(response => response || err);
+ }
+
+ return err;
+ });
+ })
+ );
+});
diff --git a/views/unsupported.erb b/views/unsupported.erb
index a01b7c7e..77064160 100644
--- a/views/unsupported.erb
+++ b/views/unsupported.erb
@@ -11,9 +11,9 @@
<p class="_fail-text">DevDocs is an API documentation browser which supports the following browsers:</p>
<ul class="_fail-list">
<li>Recent versions of Firefox, Chrome, or Opera</li>
- <li>Safari 9.1+</li>
+ <li>Safari 11.1+</li>
<li>Edge 16+</li>
- <li>iOS 10+</li>
+ <li>iOS 11.3+</li>
</ul>
<p class="_fail-text">
If you're unable to upgrade, we apologize.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]