docbrown/app/views/service_worker/index.js.erb

145 lines
5.8 KiB
Text

// Serviceworkers file. This code gets installed in users browsers and runs code before the request is made.
<% unless Rails.env.test? %>
const staticCacheName = 'static-1.2';
const expectedCaches = [
staticCacheName
];
self.addEventListener('install', event => {
self.skipWaiting();
// Populate initial serviceworker cache.
event.waitUntil(
caches.open(staticCacheName)
.then(cache => cache.addAll([
"/shell_top", // head, top bar, inline styles
"/shell_bottom", // footer
"/async_info/shell_version", // For comparing changes in the shell. Should be incremented with style changes.
"/404.html", // Not found page
"/500.html", // Error page
"/offline.html" //Offline page
]))
);
});
// remove caches that aren't in expectedCaches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) return caches.delete(key);
})
))
);
});
// Create a composed streamed webpage with shell and core content
function createPageStream(request) {
const stream = new ReadableStream({
start(controller) {
if (!caches.match('/shell_top') || !caches.match('/shell_bottom')) { //return if shell isn't cached.
return
}
// the body url is the request url plus 'include'
const url = new URL(request.url);
url.searchParams.set('i', 'i'); // Adds ?i=i or &i=i, which is our indicator for "internal" partial page
const startFetch = caches.match('/shell_top');
const endFetch = caches.match('/shell_bottom');
const middleFetch = fetch(url).then(response => {
if (!response.ok && response.status === 404) {
return caches.match('/404.html');
}
if (!response.ok && response.status != 404) {
return caches.match('/500.html');
}
return response;
}).catch(err => caches.match('/offline.html'));
function pushStream(stream) {
const reader = stream.getReader();
return reader.read().then(function process(result) {
if (result.done) return;
controller.enqueue(result.value);
return reader.read().then(process);
});
}
startFetch
.then(response => pushStream(response.body))
.then(() => middleFetch)
.then(response => pushStream(response.body))
.then(() => endFetch)
.then(response => pushStream(response.body))
.then(() => controller.close());
}
});
return new Response(stream, {
headers: {'Content-Type': 'text/html; charset=utf-8'}
});
}
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (/iPhone|CriOS|iPad/i.test(navigator.userAgent) && event.request.referrer.includes('t.co')) {
// Twitter on iOS seems to cause problems
return;
}
if (url.origin === location.origin) {
if (event.clientId === "" && // Not fetched via AJAX after page load.
event.request.method == "GET" && // Don't fetch on POST, DELETE, etc.
!event.request.referrer.includes('/signout_confirm') && // If this is the referrer, we instead want to flush.
!url.href.includes('i=i') && // Parameter representing "internal" navigation.
!url.href.includes('?signin') && // Don't run on sign in.
!url.href.includes('.js') && // Don't run on JS.
!url.href.includes('.css') && // Don't run on CSS.
!url.href.includes('/onboarding') && // Don't run on onboarding.
!url.href.includes('/api/') && // Don't run on API endpoints.
!url.href.includes('/users/auth') && // Don't run on authentication.
!url.href.includes('/shell_') && // Don't fetch for shell.
!url.href.includes('/admin') && // Don't fetch for administrate dashboard.
!url.href.includes('/internal') && // Don't fetch for internal dashboard.
!url.href.includes('/future') && // Skip for /future.
!url.href.includes('?preview=') && // Skip for /future.
!url.href.includes('/delayed_job') && // Skip for Delayed Job dashboard
!url.href.includes('/sidekiq') && // Skip for Sidekiq dashboard
caches.match('/shell_top') && // Ensure shell_top is in the cache.
caches.match('/shell_bottom')) { // Ensure shell_bottom is in the cache.
event.respondWith(createPageStream(event.request)); // Respond with the stream
// Ping version endpoint to see if we should fetch new shell.
if (!caches.match('/async_info/shell_version')) { // Check if we have a cached shell version
caches.open(staticCacheName)
.then(cache => cache.addAll([
"/async_info/shell_version",
]));
return;
}
fetch('/async_info/shell_version').then(response => response.json()).then(json => {
caches.match('/async_info/shell_version').then(cachedResponse => cachedResponse.json()).then(cacheJson => {
if (cacheJson['version'] != json['version']) {
caches.open(staticCacheName)
.then(cache => cache.addAll([
"/shell_top",
"/shell_bottom",
"/async_info/shell_version"
]));
}
})
})
return;
}
// Fetch new shell upon events that signify change in session.
if (event.clientId === "" &&
(event.request.referrer.includes('/signout_confirm') || url.href.includes('?signin') || url.href.includes('/onboarding'))) {
caches.open(staticCacheName)
.then(cache => cache.addAll([
"/shell_top",
"/shell_bottom",
]));
}
}
});
<% end %>