├── .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 |
--------------------------------------------------------------------------------