1 | 'use strict';
|
2 |
|
3 | if (typeof DEBUG === 'undefined') {
|
4 | var DEBUG = false;
|
5 | }
|
6 |
|
7 | function WebpackServiceWorker(params, helpers) {
|
8 | var loaders = helpers.loaders;
|
9 | var cacheMaps = helpers.cacheMaps;
|
10 |
|
11 | var strategy = params.strategy;
|
12 | var responseStrategy = params.responseStrategy;
|
13 |
|
14 | var assets = params.assets;
|
15 | var loadersMap = params.loaders || {};
|
16 |
|
17 | var hashesMap = params.hashesMap;
|
18 | var externals = params.externals;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | var CACHE_PREFIX = params.name;
|
26 | var CACHE_TAG = params.version;
|
27 | var CACHE_NAME = CACHE_PREFIX + ':' + CACHE_TAG;
|
28 |
|
29 | var STORED_DATA_KEY = '__offline_webpack__data';
|
30 |
|
31 | mapAssets();
|
32 |
|
33 | var allAssets = [].concat(assets.main, assets.additional, assets.optional);
|
34 | var navigateFallbackURL = params.navigateFallbackURL;
|
35 |
|
36 | self.addEventListener('install', function (event) {
|
37 | console.log('[SW]:', 'Install event');
|
38 |
|
39 | var installing = undefined;
|
40 |
|
41 | if (strategy === 'changed') {
|
42 | installing = cacheChanged('main');
|
43 | } else {
|
44 | installing = cacheAssets('main');
|
45 | }
|
46 |
|
47 | event.waitUntil(installing);
|
48 | });
|
49 |
|
50 | self.addEventListener('activate', function (event) {
|
51 | console.log('[SW]:', 'Activate event');
|
52 |
|
53 | var activation = cacheAdditional();
|
54 |
|
55 |
|
56 |
|
57 | activation = activation.then(storeCacheData);
|
58 | activation = activation.then(deleteObsolete);
|
59 | activation = activation.then(function () {
|
60 | if (self.clients && self.clients.claim) {
|
61 | return self.clients.claim();
|
62 | }
|
63 | });
|
64 |
|
65 | event.waitUntil(activation);
|
66 | });
|
67 |
|
68 | function cacheAdditional() {
|
69 | if (!assets.additional.length) {
|
70 | return Promise.resolve();
|
71 | }
|
72 |
|
73 | if (DEBUG) {
|
74 | console.log('[SW]:', 'Caching additional');
|
75 | }
|
76 |
|
77 | var operation = undefined;
|
78 |
|
79 | if (strategy === 'changed') {
|
80 | operation = cacheChanged('additional');
|
81 | } else {
|
82 | operation = cacheAssets('additional');
|
83 | }
|
84 |
|
85 |
|
86 | return operation['catch'](function (e) {
|
87 | console.error('[SW]:', 'Cache section `additional` failed to load');
|
88 | });
|
89 | }
|
90 |
|
91 | function cacheAssets(section) {
|
92 | var batch = assets[section];
|
93 |
|
94 | return caches.open(CACHE_NAME).then(function (cache) {
|
95 | return addAllNormalized(cache, batch, {
|
96 | bust: params.version,
|
97 | request: params.prefetchRequest
|
98 | });
|
99 | }).then(function () {
|
100 | logGroup('Cached assets: ' + section, batch);
|
101 | })['catch'](function (e) {
|
102 | console.error(e);
|
103 | throw e;
|
104 | });
|
105 | }
|
106 |
|
107 | function cacheChanged(section) {
|
108 | return getLastCache().then(function (args) {
|
109 | if (!args) {
|
110 | return cacheAssets(section);
|
111 | }
|
112 |
|
113 | var lastCache = args[0];
|
114 | var lastKeys = args[1];
|
115 | var lastData = args[2];
|
116 |
|
117 | var lastMap = lastData.hashmap;
|
118 | var lastVersion = lastData.version;
|
119 |
|
120 | if (!lastData.hashmap || lastVersion === params.version) {
|
121 | return cacheAssets(section);
|
122 | }
|
123 |
|
124 | var lastHashedAssets = Object.keys(lastMap).map(function (hash) {
|
125 | return lastMap[hash];
|
126 | });
|
127 |
|
128 | var lastUrls = lastKeys.map(function (req) {
|
129 | var url = new URL(req.url);
|
130 | url.search = '';
|
131 |
|
132 | return url.toString();
|
133 | });
|
134 |
|
135 | var sectionAssets = assets[section];
|
136 | var moved = [];
|
137 | var changed = sectionAssets.filter(function (url) {
|
138 | if (lastUrls.indexOf(url) === -1 || lastHashedAssets.indexOf(url) === -1) {
|
139 | return true;
|
140 | }
|
141 |
|
142 | return false;
|
143 | });
|
144 |
|
145 | Object.keys(hashesMap).forEach(function (hash) {
|
146 | var asset = hashesMap[hash];
|
147 |
|
148 |
|
149 | if (sectionAssets.indexOf(asset) === -1 || changed.indexOf(asset) !== -1 || moved.indexOf(asset) !== -1) return;
|
150 |
|
151 | var lastAsset = lastMap[hash];
|
152 |
|
153 | if (lastAsset && lastUrls.indexOf(lastAsset) !== -1) {
|
154 | moved.push([lastAsset, asset]);
|
155 | } else {
|
156 | changed.push(asset);
|
157 | }
|
158 | });
|
159 |
|
160 | logGroup('Changed assets: ' + section, changed);
|
161 | logGroup('Moved assets: ' + section, moved);
|
162 |
|
163 | var movedResponses = Promise.all(moved.map(function (pair) {
|
164 | return lastCache.match(pair[0]).then(function (response) {
|
165 | return [pair[1], response];
|
166 | });
|
167 | }));
|
168 |
|
169 | return caches.open(CACHE_NAME).then(function (cache) {
|
170 | var move = movedResponses.then(function (responses) {
|
171 | return Promise.all(responses.map(function (pair) {
|
172 | return cache.put(pair[0], pair[1]);
|
173 | }));
|
174 | });
|
175 |
|
176 | return Promise.all([move, addAllNormalized(cache, changed, {
|
177 | bust: params.version,
|
178 | request: params.prefetchRequest
|
179 | })]);
|
180 | });
|
181 | });
|
182 | }
|
183 |
|
184 | function deleteObsolete() {
|
185 | return caches.keys().then(function (keys) {
|
186 | var all = keys.map(function (key) {
|
187 | if (key.indexOf(CACHE_PREFIX) !== 0 || key.indexOf(CACHE_NAME) === 0) return;
|
188 |
|
189 | console.log('[SW]:', 'Delete cache:', key);
|
190 | return caches['delete'](key);
|
191 | });
|
192 |
|
193 | return Promise.all(all);
|
194 | });
|
195 | }
|
196 |
|
197 | function getLastCache() {
|
198 | return caches.keys().then(function (keys) {
|
199 | var index = keys.length;
|
200 | var key = undefined;
|
201 |
|
202 | while (index--) {
|
203 | key = keys[index];
|
204 |
|
205 | if (key.indexOf(CACHE_PREFIX) === 0) {
|
206 | break;
|
207 | }
|
208 | }
|
209 |
|
210 | if (!key) return;
|
211 |
|
212 | var cache = undefined;
|
213 |
|
214 | return caches.open(key).then(function (_cache) {
|
215 | cache = _cache;
|
216 | return _cache.match(new URL(STORED_DATA_KEY, location).toString());
|
217 | }).then(function (response) {
|
218 | if (!response) return;
|
219 |
|
220 | return Promise.all([cache, cache.keys(), response.json()]);
|
221 | });
|
222 | });
|
223 | }
|
224 |
|
225 | function storeCacheData() {
|
226 | return caches.open(CACHE_NAME).then(function (cache) {
|
227 | var data = new Response(JSON.stringify({
|
228 | version: params.version,
|
229 | hashmap: hashesMap
|
230 | }));
|
231 |
|
232 | return cache.put(new URL(STORED_DATA_KEY, location).toString(), data);
|
233 | });
|
234 | }
|
235 |
|
236 | self.addEventListener('fetch', function (event) {
|
237 | var requestUrl = event.request.url;
|
238 | var url = new URL(requestUrl);
|
239 | var urlString = undefined;
|
240 |
|
241 | if (externals.indexOf(requestUrl) !== -1) {
|
242 | urlString = requestUrl;
|
243 | } else {
|
244 | url.search = '';
|
245 | urlString = url.toString();
|
246 | }
|
247 |
|
248 |
|
249 | var isGET = event.request.method === 'GET';
|
250 | var assetMatches = allAssets.indexOf(urlString) !== -1;
|
251 | var cacheUrl = urlString;
|
252 |
|
253 | if (!assetMatches) {
|
254 | var cacheRewrite = matchCacheMap(event.request);
|
255 |
|
256 | if (cacheRewrite) {
|
257 | cacheUrl = cacheRewrite;
|
258 | assetMatches = true;
|
259 | }
|
260 | }
|
261 |
|
262 | if (!assetMatches && isGET) {
|
263 |
|
264 |
|
265 | if (navigateFallbackURL && isNavigateRequest(event.request)) {
|
266 | event.respondWith(handleNavigateFallback(fetch(event.request)));
|
267 |
|
268 | return;
|
269 | }
|
270 | }
|
271 |
|
272 | if (!assetMatches || !isGET) {
|
273 |
|
274 | if (url.origin !== location.origin && navigator.userAgent.indexOf('Firefox/44.') !== -1) {
|
275 | event.respondWith(fetch(event.request));
|
276 | }
|
277 |
|
278 | return;
|
279 | }
|
280 |
|
281 |
|
282 |
|
283 |
|
284 | var resource = undefined;
|
285 |
|
286 | if (responseStrategy === "network-first") {
|
287 | resource = networkFirstResponse(event, urlString, cacheUrl);
|
288 | }
|
289 |
|
290 |
|
291 | else {
|
292 | resource = cacheFirstResponse(event, urlString, cacheUrl);
|
293 | }
|
294 |
|
295 | if (navigateFallbackURL && isNavigateRequest(event.request)) {
|
296 | resource = handleNavigateFallback(resource);
|
297 | }
|
298 |
|
299 | event.respondWith(resource);
|
300 | });
|
301 |
|
302 | self.addEventListener('message', function (e) {
|
303 | var data = e.data;
|
304 | if (!data) return;
|
305 |
|
306 | switch (data.action) {
|
307 | case 'skipWaiting':
|
308 | {
|
309 | if (self.skipWaiting) self.skipWaiting();
|
310 | }break;
|
311 | }
|
312 | });
|
313 |
|
314 | function cacheFirstResponse(event, urlString, cacheUrl) {
|
315 | return cachesMatch(cacheUrl, CACHE_NAME).then(function (response) {
|
316 | if (response) {
|
317 | if (DEBUG) {
|
318 | console.log('[SW]:', 'URL [' + cacheUrl + '](' + urlString + ') from cache');
|
319 | }
|
320 |
|
321 | return response;
|
322 | }
|
323 |
|
324 |
|
325 | var fetching = fetch(event.request).then(function (response) {
|
326 | if (!response.ok) {
|
327 | if (DEBUG) {
|
328 | console.log('[SW]:', 'URL [' + urlString + '] wrong response: [' + response.status + '] ' + response.type);
|
329 | }
|
330 |
|
331 | return response;
|
332 | }
|
333 |
|
334 | if (DEBUG) {
|
335 | console.log('[SW]:', 'URL [' + urlString + '] from network');
|
336 | }
|
337 |
|
338 | if (cacheUrl === urlString) {
|
339 | (function () {
|
340 | var responseClone = response.clone();
|
341 |
|
342 | caches.open(CACHE_NAME).then(function (cache) {
|
343 | return cache.put(urlString, responseClone);
|
344 | }).then(function () {
|
345 | console.log('[SW]:', 'Cache asset: ' + urlString);
|
346 | });
|
347 | })();
|
348 | }
|
349 |
|
350 | return response;
|
351 | });
|
352 |
|
353 | return fetching;
|
354 | });
|
355 | }
|
356 |
|
357 | function networkFirstResponse(event, urlString, cacheUrl) {
|
358 | return fetch(event.request).then(function (response) {
|
359 | if (response.ok) {
|
360 | if (DEBUG) {
|
361 | console.log('[SW]:', 'URL [' + urlString + '] from network');
|
362 | }
|
363 |
|
364 | return response;
|
365 | }
|
366 |
|
367 |
|
368 | throw new Error("response is not ok");
|
369 | })
|
370 |
|
371 |
|
372 | ['catch'](function () {
|
373 | if (DEBUG) {
|
374 | console.log('[SW]:', 'URL [' + urlString + '] from cache if possible');
|
375 | }
|
376 |
|
377 | return cachesMatch(cacheUrl, CACHE_NAME);
|
378 | });
|
379 | }
|
380 |
|
381 | function handleNavigateFallback(fetching) {
|
382 | return fetching['catch'](function () {}).then(function (response) {
|
383 | if (!response || !response.ok) {
|
384 | if (DEBUG) {
|
385 | console.log('[SW]:', 'Loading navigation fallback [' + navigateFallbackURL + '] from cache');
|
386 | }
|
387 |
|
388 | return cachesMatch(navigateFallbackURL, CACHE_NAME);
|
389 | }
|
390 |
|
391 | return response;
|
392 | });
|
393 | }
|
394 |
|
395 | function mapAssets() {
|
396 | Object.keys(assets).forEach(function (key) {
|
397 | assets[key] = assets[key].map(function (path) {
|
398 | var url = new URL(path, location);
|
399 |
|
400 | if (externals.indexOf(path) === -1) {
|
401 | url.search = '';
|
402 | } else {
|
403 |
|
404 | url.hash = '';
|
405 | }
|
406 |
|
407 | return url.toString();
|
408 | });
|
409 | });
|
410 |
|
411 | Object.keys(loadersMap).forEach(function (key) {
|
412 | loadersMap[key] = loadersMap[key].map(function (path) {
|
413 | var url = new URL(path, location);
|
414 |
|
415 | if (externals.indexOf(path) === -1) {
|
416 | url.search = '';
|
417 | } else {
|
418 |
|
419 | url.hash = '';
|
420 | }
|
421 |
|
422 | return url.toString();
|
423 | });
|
424 | });
|
425 |
|
426 | hashesMap = Object.keys(hashesMap).reduce(function (result, hash) {
|
427 | var url = new URL(hashesMap[hash], location);
|
428 | url.search = '';
|
429 |
|
430 | result[hash] = url.toString();
|
431 | return result;
|
432 | }, {});
|
433 |
|
434 | externals = externals.map(function (path) {
|
435 | var url = new URL(path, location);
|
436 | url.hash = '';
|
437 |
|
438 | return url.toString();
|
439 | });
|
440 | }
|
441 |
|
442 | function addAllNormalized(cache, requests, options) {
|
443 | var allowLoaders = options.allowLoaders !== false;
|
444 | var bustValue = options && options.bust;
|
445 | var requestInit = options.request || {
|
446 | credentials: 'omit',
|
447 | mode: 'cors'
|
448 | };
|
449 |
|
450 | return Promise.all(requests.map(function (request) {
|
451 | if (bustValue) {
|
452 | request = applyCacheBust(request, bustValue);
|
453 | }
|
454 |
|
455 | return fetch(request, requestInit);
|
456 | })).then(function (responses) {
|
457 | if (responses.some(function (response) {
|
458 | return !response.ok;
|
459 | })) {
|
460 | return Promise.reject(new Error('Wrong response status'));
|
461 | }
|
462 |
|
463 | var extracted = [];
|
464 | var addAll = responses.map(function (response, i) {
|
465 | if (allowLoaders) {
|
466 | extracted.push(extractAssetsWithLoaders(requests[i], response));
|
467 | }
|
468 |
|
469 | return cache.put(requests[i], response);
|
470 | });
|
471 |
|
472 | if (extracted.length) {
|
473 | (function () {
|
474 | var newOptions = copyObject(options);
|
475 | newOptions.allowLoaders = false;
|
476 |
|
477 | var waitAll = addAll;
|
478 |
|
479 | addAll = Promise.all(extracted).then(function (all) {
|
480 | var extractedRequests = [].concat.apply([], all);
|
481 |
|
482 | if (requests.length) {
|
483 | waitAll = waitAll.concat(addAllNormalized(cache, extractedRequests, newOptions));
|
484 | }
|
485 |
|
486 | return Promise.all(waitAll);
|
487 | });
|
488 | })();
|
489 | } else {
|
490 | addAll = Promise.all(addAll);
|
491 | }
|
492 |
|
493 | return addAll;
|
494 | });
|
495 | }
|
496 |
|
497 | function extractAssetsWithLoaders(request, response) {
|
498 | var all = Object.keys(loadersMap).map(function (key) {
|
499 | var loader = loadersMap[key];
|
500 |
|
501 | if (loader.indexOf(request) !== -1 && loaders[key]) {
|
502 | return loaders[key](response.clone());
|
503 | }
|
504 | }).filter(function (a) {
|
505 | return !!a;
|
506 | });
|
507 |
|
508 | return Promise.all(all).then(function (all) {
|
509 | return [].concat.apply([], all);
|
510 | });
|
511 | }
|
512 |
|
513 | function matchCacheMap(request) {
|
514 | var urlString = request.url;
|
515 | var url = new URL(urlString);
|
516 |
|
517 | var requestType = undefined;
|
518 |
|
519 | if (request.mode === 'navigate') {
|
520 | requestType = 'navigate';
|
521 | } else if (url.origin === location.origin) {
|
522 | requestType = 'same-origin';
|
523 | } else {
|
524 | requestType = 'cross-origin';
|
525 | }
|
526 |
|
527 | for (var i = 0; i < cacheMaps.length; i++) {
|
528 | var map = cacheMaps[i];
|
529 |
|
530 | if (!map) continue;
|
531 | if (map.requestTypes && map.requestTypes.indexOf(requestType) === -1) {
|
532 | continue;
|
533 | }
|
534 |
|
535 | var newString = undefined;
|
536 |
|
537 | if (typeof map.match === 'function') {
|
538 | newString = map.match(url, request);
|
539 | } else {
|
540 | newString = urlString.replace(map.match, map.to);
|
541 | }
|
542 |
|
543 | if (newString && newString !== urlString) {
|
544 | return newString;
|
545 | }
|
546 | }
|
547 | }
|
548 | }
|
549 |
|
550 | function cachesMatch(request, cacheName) {
|
551 | return caches.match(request, {
|
552 | cacheName: cacheName
|
553 | })
|
554 |
|
555 | ['catch'](function () {});
|
556 | }
|
557 |
|
558 | function applyCacheBust(asset, key) {
|
559 | var hasQuery = asset.indexOf('?') !== -1;
|
560 | return asset + (hasQuery ? '&' : '?') + '__uncache=' + encodeURIComponent(key);
|
561 | }
|
562 |
|
563 | function getClientsURLs() {
|
564 | if (!self.clients) {
|
565 | return Promise.resolve([]);
|
566 | }
|
567 |
|
568 | return self.clients.matchAll({
|
569 | includeUncontrolled: true
|
570 | }).then(function (clients) {
|
571 | if (!clients.length) return [];
|
572 |
|
573 | var result = [];
|
574 |
|
575 | clients.forEach(function (client) {
|
576 | var url = new URL(client.url);
|
577 | url.search = '';
|
578 | url.hash = '';
|
579 | var urlString = url.toString();
|
580 |
|
581 | if (!result.length || result.indexOf(urlString) === -1) {
|
582 | result.push(urlString);
|
583 | }
|
584 | });
|
585 |
|
586 | return result;
|
587 | });
|
588 | }
|
589 |
|
590 | function isNavigateRequest(request) {
|
591 | return request.mode === 'navigate' || request.headers.get('Upgrade-Insecure-Requests') || (request.headers.get('Accept') || '').indexOf('text/html') !== -1;
|
592 | }
|
593 |
|
594 | function copyObject(original) {
|
595 | return Object.keys(original).reduce(function (result, key) {
|
596 | result[key] = original[key];
|
597 | return result;
|
598 | }, {});
|
599 | }
|
600 |
|
601 | function logGroup(title, assets) {
|
602 | console.groupCollapsed('[SW]:', title);
|
603 |
|
604 | assets.forEach(function (asset) {
|
605 | console.log('Asset:', asset);
|
606 | });
|
607 |
|
608 | console.groupEnd();
|
609 | } |
\ | No newline at end of file |