├── .gitignore ├── README.md ├── examples └── 1 │ ├── README.md │ ├── index.html │ ├── main.js │ ├── styles.css │ ├── sw.browserify.js │ └── sw.js ├── index.js ├── lib └── serviceworker-caches.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Service Worker - Cache All 2 | 3 | A simple, bare minimum service worker that would cache all the requests, 4 | and can be used as a boiler plate to immediately take your webapp offline. 5 | 6 | Explanation is annotated in the source code - `index.js`. 7 | 8 | ## Check examples for usage 9 | 10 | For online example see https://boopathi.github.io/sw-cache-all/ 11 | 12 | ## LICENSE 13 | 14 | https://boopathi.mit-license.org/2015 15 | -------------------------------------------------------------------------------- /examples/1/README.md: -------------------------------------------------------------------------------- 1 | # Cats 2 | 3 | ## build sw.js 4 | 5 | + `browserify sw.browserify.js -o sw.js` 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Photos of Cat

8 | 9 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/1/main.js: -------------------------------------------------------------------------------- 1 | function jsonFlickrApi(response) { 2 | var cats = $("#cats"); 3 | $.each(response.photos.photo, function(i, item) { 4 | var src = "https://farm" 5 | + item.farm 6 | + ".static.flickr.com/" 7 | + item.server 8 | + "/" + item.id 9 | + "_" + item.secret 10 | + "_n.jpg"; 11 | var container = $("
  • ").appendTo(cats); 12 | $("").attr("src", src).appendTo(container); 13 | }); 14 | } 15 | 16 | $(function() { 17 | $.ajax({ 18 | url: flickr_api_url, 19 | method: 'GET', 20 | dataType: 'jsonp', 21 | jsonpCallback: 'jsonFlickrApi', 22 | cache: true, 23 | data: { 24 | tags: 'cat', 25 | safe_search: 1, 26 | per_page: 20 27 | }, 28 | error: function(err) { 29 | console.log(err); 30 | } 31 | }); 32 | }); 33 | 34 | if(navigator.serviceWorker) { 35 | navigator 36 | .serviceWorker 37 | .register('sw.js') 38 | .then(function(r) { 39 | console.log('Cats are now available offline'); 40 | }) 41 | .catch(function(e) { 42 | console.log('Cats are NOT available offline'); 43 | console.log(e); 44 | }); 45 | } else { 46 | console.log('Service workers are not supported'); 47 | } -------------------------------------------------------------------------------- /examples/1/styles.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | } 7 | ul, li { 8 | list-style: none; 9 | } 10 | h1 { 11 | background: black; 12 | font-size: 16px; 13 | border: solid 10px white; 14 | color: white; 15 | font-family: 'Ubuntu Mono', sans-serif; 16 | text-align: center; 17 | padding: 10px; 18 | } 19 | li { 20 | border: solid 10px white; 21 | border-top: 0; 22 | text-align: center; 23 | background: black; 24 | } 25 | img { 26 | min-height: 50px; 27 | max-width: 100%; 28 | } -------------------------------------------------------------------------------- /examples/1/sw.browserify.js: -------------------------------------------------------------------------------- 1 | // Set the CACHE_NAME first 2 | self.CACHE_NAME = 'cats-pictures-v1'; 3 | 4 | // and require the sw-cache-all 5 | require('../../index.js'); 6 | -------------------------------------------------------------------------------- /examples/1/sw.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o binding: 98 | var sequence = []; 99 | 100 | requests = requests.map(function(request) { 101 | if (request instanceof Request) { 102 | return request; 103 | } 104 | else { 105 | return String(request); // may throw TypeError 106 | } 107 | }); 108 | 109 | return Promise.all( 110 | requests.map(function(request) { 111 | if (typeof request === 'string') { 112 | request = new Request(request); 113 | } 114 | 115 | var scheme = new URL(request.url).protocol; 116 | 117 | if (scheme !== 'http:' && scheme !== 'https:') { 118 | throw new NetworkError("Invalid scheme"); 119 | } 120 | 121 | return fetch(request.clone()); 122 | }) 123 | ); 124 | }).then(function(responses) { 125 | // TODO: check that requests don't overwrite one another 126 | // (don't think this is possible to polyfill due to opaque responses) 127 | return Promise.all( 128 | responses.map(function(response, i) { 129 | return cache.put(requests[i], response); 130 | }) 131 | ); 132 | }).then(function() { 133 | return undefined; 134 | }); 135 | }; 136 | } 137 | 138 | if (!CacheStorage.prototype.match) { 139 | // This is probably vulnerable to race conditions (removing caches etc) 140 | CacheStorage.prototype.match = function match(request, opts) { 141 | var caches = this; 142 | 143 | return this.keys().then(function(cacheNames) { 144 | var match; 145 | 146 | return cacheNames.reduce(function(chain, cacheName) { 147 | return chain.then(function() { 148 | return match || caches.open(cacheName).then(function(cache) { 149 | return cache.match(request, opts); 150 | }).then(function(response) { 151 | match = response; 152 | return match; 153 | }); 154 | }); 155 | }, Promise.resolve()); 156 | }); 157 | }; 158 | } 159 | 160 | module.exports = self.caches; 161 | },{}]},{},[1]); 162 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // caches polyfill because it is not added to native yet! 2 | var caches = require('./lib/serviceworker-caches'); 3 | 4 | if(typeof self.CACHE_NAME !== 'string') { 5 | throw new Error('Cache Name cannot be empty'); 6 | } 7 | 8 | self.addEventListener('fetch', function(event) { 9 | 10 | // Clone the request for fetch and cache 11 | // A request is a stream and can be consumed only once. 12 | var fetchRequest = event.request.clone(), 13 | cacheRequest = event.request.clone(); 14 | 15 | // Respond with content from fetch or cache 16 | event.respondWith( 17 | 18 | // Try fetch 19 | fetch(fetchRequest) 20 | 21 | // when fetch is successful, we update the cache 22 | .then(function(response) { 23 | 24 | // A response is a stream and can be consumed only once. 25 | // Because we want the browser to consume the response, 26 | // as well as cache to consume the response, we need to 27 | // clone it so we have 2 streams 28 | var responseToCache = response.clone(); 29 | 30 | // and update the cache 31 | caches 32 | .open(self.CACHE_NAME) 33 | .then(function(cache) { 34 | 35 | // Clone the request again to use it 36 | // as the key for our cache 37 | var cacheSaveRequest = event.request.clone(); 38 | cache.put(cacheSaveRequest, responseToCache); 39 | 40 | }); 41 | 42 | // Return the response stream to be consumed by browser 43 | return response; 44 | 45 | }) 46 | 47 | // when fetch times out or fails 48 | .catch(function(err) { 49 | 50 | // Return the promise which 51 | // resolves on a match in cache for the current request 52 | // ot rejects if no matches are found 53 | return caches.match(cacheRequest); 54 | 55 | }) 56 | ); 57 | }); 58 | 59 | // Now we need to clean up resources in the previous versions 60 | // of Service Worker scripts 61 | self.addEventListener('activate', function(event) { 62 | 63 | // Destroy the cache 64 | event.waitUntil(caches.delete(self.CACHE_NAME)); 65 | 66 | }); -------------------------------------------------------------------------------- /lib/serviceworker-caches.js: -------------------------------------------------------------------------------- 1 | if (!Cache.prototype.add) { 2 | Cache.prototype.add = function add(request) { 3 | return this.addAll([request]); 4 | }; 5 | } 6 | 7 | if (!Cache.prototype.addAll) { 8 | Cache.prototype.addAll = function addAll(requests) { 9 | var cache = this; 10 | 11 | // Since DOMExceptions are not constructable: 12 | function NetworkError(message) { 13 | this.name = 'NetworkError'; 14 | this.code = 19; 15 | this.message = message; 16 | } 17 | NetworkError.prototype = Object.create(Error.prototype); 18 | 19 | return Promise.resolve().then(function() { 20 | if (arguments.length < 1) throw new TypeError(); 21 | 22 | // Simulate sequence<(Request or USVString)> binding: 23 | var sequence = []; 24 | 25 | requests = requests.map(function(request) { 26 | if (request instanceof Request) { 27 | return request; 28 | } 29 | else { 30 | return String(request); // may throw TypeError 31 | } 32 | }); 33 | 34 | return Promise.all( 35 | requests.map(function(request) { 36 | if (typeof request === 'string') { 37 | request = new Request(request); 38 | } 39 | 40 | var scheme = new URL(request.url).protocol; 41 | 42 | if (scheme !== 'http:' && scheme !== 'https:') { 43 | throw new NetworkError("Invalid scheme"); 44 | } 45 | 46 | return fetch(request.clone()); 47 | }) 48 | ); 49 | }).then(function(responses) { 50 | // TODO: check that requests don't overwrite one another 51 | // (don't think this is possible to polyfill due to opaque responses) 52 | return Promise.all( 53 | responses.map(function(response, i) { 54 | return cache.put(requests[i], response); 55 | }) 56 | ); 57 | }).then(function() { 58 | return undefined; 59 | }); 60 | }; 61 | } 62 | 63 | if (!CacheStorage.prototype.match) { 64 | // This is probably vulnerable to race conditions (removing caches etc) 65 | CacheStorage.prototype.match = function match(request, opts) { 66 | var caches = this; 67 | 68 | return this.keys().then(function(cacheNames) { 69 | var match; 70 | 71 | return cacheNames.reduce(function(chain, cacheName) { 72 | return chain.then(function() { 73 | return match || caches.open(cacheName).then(function(cache) { 74 | return cache.match(request, opts); 75 | }).then(function(response) { 76 | match = response; 77 | return match; 78 | }); 79 | }); 80 | }, Promise.resolve()); 81 | }); 82 | }; 83 | } 84 | 85 | module.exports = self.caches; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sw-cache-all", 3 | "version": "0.1.0", 4 | "description": "A Service Worker shim to cache all requests and respond with the latest cached content", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/boopathi/sw-cache-all" 12 | }, 13 | "keywords": [ 14 | "cache-all", 15 | "service", 16 | "worker", 17 | "sw" 18 | ], 19 | "author": "boopathi", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/boopathi/sw-cache-all/issues" 23 | }, 24 | "homepage": "https://github.com/boopathi/sw-cache-all" 25 | } 26 | --------------------------------------------------------------------------------