service-worker.js.erb 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. <%# The name of the cache to store responses in %>
  2. <%# If the cache name changes DevDocs is assumed to be updated %>
  3. const cacheName = '<%= service_worker_cache_name %>';
  4. <%# Url's to cache when the service worker is installed %>
  5. const urlsToCache = [
  6. '/',
  7. '/favicon.ico',
  8. '/manifest.json',
  9. '<%= service_worker_asset_urls.join "',\n '" %>',
  10. '<%= doc_index_urls.join "',\n '" %>',
  11. ];
  12. <%# Clone a request with the mode set to 'cors' %>
  13. function corsify(request) {
  14. const options = {
  15. mode: 'cors',
  16. };
  17. const keys = [
  18. 'body',
  19. 'cache',
  20. 'credentials',
  21. 'headers',
  22. 'integrity',
  23. 'keepalive',
  24. 'method',
  25. 'redirect',
  26. 'referrer',
  27. 'referrerPolicy',
  28. 'signal',
  29. 'window',
  30. ];
  31. for (const key of keys) {
  32. options[key] = request[key];
  33. }
  34. return new Request(request.url, options);
  35. }
  36. <%# Set-up the cache %>
  37. self.addEventListener('install', event => {
  38. self.skipWaiting();
  39. event.waitUntil(
  40. caches.open(cacheName).then(cache => cache.addAll(urlsToCache)),
  41. );
  42. });
  43. <%# Remove old caches %>
  44. self.addEventListener('activate', event => {
  45. event.waitUntil((async () => {
  46. const keys = await caches.keys();
  47. const jobs = keys.map(key => key !== cacheName ? caches.delete(key) : Promise.resolve());
  48. return Promise.all(jobs);
  49. })());
  50. });
  51. <%# Handle HTTP requests %>
  52. self.addEventListener('fetch', event => {
  53. event.respondWith((async () => {
  54. const cachedResponse = await caches.match(event.request);
  55. if (cachedResponse) return cachedResponse;
  56. try {
  57. const response = await fetch(corsify(event.request));
  58. <%# If the status code is 0 it means the response is opaque %>
  59. <%# If the response is opaque it's not possible to read whether it is successful or not, so we assume it is %>
  60. if (!response.ok && response.status !== 0) {
  61. throw new Error(`The HTTP request failed with status code ${response.status}`);
  62. }
  63. return response;
  64. } catch (err) {
  65. const url = new URL(event.request.url);
  66. const pathname = url.pathname;
  67. const filename = pathname.substr(1 + pathname.lastIndexOf('/')).split(/\#|\?/g)[0];
  68. const extensions = ['.html', '.css', '.js', '.json', '.png', '.ico', '.svg', '.xml'];
  69. <%# Attempt to return the index page from the cache if the user is visiting a url like devdocs.io/offline or devdocs.io/javascript/global_objects/array/find %>
  70. <%# The index page will make sure the correct documentation or a proper offline page is shown %>
  71. if (url.origin === location.origin && !extensions.some(ext => filename.endsWith(ext))) {
  72. const cachedIndex = await caches.match('/');
  73. if (cachedIndex) return cachedIndex;
  74. }
  75. throw err;
  76. }
  77. })());
  78. });