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