UNPKG

@lcdp/offline-plugin

Version:
264 lines (221 loc) 7.98 kB
var appCacheIframe; // Defined by `install(options)` so that `update()` / `applyUpdate()` can // forward errors to the user's callbacks. No-op until `install()` is called. var sendEvent = function() {}; function hasSW() { <% if (typeof ServiceWorker === 'object' && ServiceWorker.force) { %> // Forced install return 'serviceWorker' in navigator; <% } else { %> return 'serviceWorker' in navigator && ( window.location.protocol === 'https:' || /^(.*\.)?localhost$/.test(window.location.hostname) || // only localhost or ends with .localhost window.location.hostname.indexOf('127.') === 0 ); <% } %> } function install(options) { options || (options = {}); sendEvent = function(event, error) { if (typeof options[event] !== 'function') return; var payload = { source: 'ServiceWorker' }; if (error) payload.error = error; options[event](payload); }; <% if (typeof ServiceWorker !== 'undefined') { %> if (hasSW()) { var registration = navigator.serviceWorker .register( <%- JSON.stringify(ServiceWorker.location) %>, { <% if (ServiceWorker.scope) { %> scope: <%- JSON.stringify(ServiceWorker.scope) %>, <% } %> <% if ( ServiceWorker.updateViaCache && ServiceWorker.updateViaCache !== 'imports' ) { %> updateViaCache: <%- JSON.stringify(ServiceWorker.updateViaCache) %>, <% } %> } ); <% if (ServiceWorker.events) { %> var handleUpdating = function(registration) { var sw = registration.installing || registration.waiting; var ignoreInstalling; var ignoreWaiting; // No SW or already handled if (!sw || sw.onstatechange) return; var stateChangeHandler; // Already has SW if (registration.active) { onUpdateStateChange(); stateChangeHandler = onUpdateStateChange; } else { onInstallStateChange(); stateChangeHandler = onInstallStateChange; } ignoreInstalling = true; if (registration.waiting) { ignoreWaiting = true; } sw.onstatechange = stateChangeHandler; function onUpdateStateChange() { switch (sw.state) { case 'redundant': { sendEvent('onUpdateFailed'); sw.onstatechange = null; } break; case 'installing': { if (!ignoreInstalling) { sendEvent('onUpdating'); } } break; case 'installed': { if (!ignoreWaiting) { sendEvent('onUpdateReady'); } } break; case 'activated': { sendEvent('onUpdated'); sw.onstatechange = null; } break; } } function onInstallStateChange() { switch (sw.state) { case 'redundant': { // Failed to install, ignore sw.onstatechange = null; } break; case 'installing': { // Installing, ignore } break; case 'installed': { // Installed, wait activation } break; case 'activated': { sendEvent('onInstalled'); sw.onstatechange = null; } break; } } }; <% } %> registration.then(function(reg) { // WTF no reg? if (!reg) return; // Installed but Shift-Reloaded (page is not controller by SW), // update might be ready at this point (more than one tab opened). // Anyway, if page is hard-reloaded, then it probably already have latest version // but it's not controlled by SW yet. Applying update will claim this page // to be controlled by SW. Maybe set flag to not reload it? // if (!navigator.serviceWorker.controller) return; <% if (ServiceWorker.events) { %> handleUpdating(reg); reg.onupdatefound = function() { handleUpdating(reg); }; <% } %> }).catch(function(err) { // Forward registration failures to options.onError; also prevents // unhandled promise rejections when no callback is set. sendEvent('onError', err); }); return; } <% } %> <% if (typeof AppCache !== 'undefined' && AppCache.disableInstall !== true) { %> if (window.applicationCache) { var directory = <%- JSON.stringify(AppCache.location) %>; var name = <%- JSON.stringify(AppCache.name) %>; var doLoad = function() { var page = directory + name + '.html'; var iframe = document.createElement('iframe'); <% if (AppCache.events) { %> window.addEventListener('message', function(e) { if (e.source !== iframe.contentWindow) return; var match = (e.data + '').match(/__offline-plugin_AppCacheEvent:(\w+)/); if (!match) return; var event = match[1]; if (typeof options[event] === 'function') { options[event]({ source: 'AppCache' }); } }); <% } %> iframe.src = page; iframe.style.display = 'none'; appCacheIframe = iframe; document.body.appendChild(iframe); }; if (document.readyState === 'complete') { setTimeout(doLoad); } else { window.addEventListener('load', doLoad); } return; } <% } %> } function applyUpdate(callback, errback) { <% if (typeof ServiceWorker !== 'undefined' && ServiceWorker.events) { %> if (hasSW()) { navigator.serviceWorker.getRegistration(<%- JSON.stringify(ServiceWorker.scope) %>).then(function(registration) { if (!registration || !registration.waiting) { errback && errback(); return; } registration.waiting.postMessage({ action: 'skipWaiting' }); callback && callback(); }).catch(function(err) { sendEvent('onError', err); errback && errback(err); }); return; } <% } %> <% if (typeof AppCache !== 'undefined' && AppCache.events && AppCache.disableInstall !== true) { %> if (appCacheIframe) { try { appCacheIframe.contentWindow.__applyUpdate(); callback && setTimeout(callback); } catch (e) { errback && setTimeout(errback); } } <% } %> } function update() { <% if (typeof ServiceWorker !== 'undefined') { %> if (hasSW()) { navigator.serviceWorker.getRegistration(<%- JSON.stringify(ServiceWorker.scope) %>).then(function(registration) { if (!registration) return; return registration.update(); }).catch(function(err) { // registration.update() rejects when the browser cannot fetch the SW // script (network error, redirect on the script source, ...). Forward // to options.onError so callers can react; also prevents the // rejection from surfacing as an unhandled promise rejection. // Note: browser-initiated update checks (every 24h or on navigation) // cannot be intercepted from JS — only explicit update() calls. sendEvent('onError', err); }); } <% } %> <% if (typeof AppCache !== 'undefined' && AppCache.disableInstall !== true) { %> if (appCacheIframe) { try { appCacheIframe.contentWindow.applicationCache.update(); } catch (e) {} } <% } %> } <% if (typeof autoUpdate !== 'undefined') { %> setInterval(update, <%- autoUpdate %>); <% } %> exports.install = install; exports.applyUpdate = applyUpdate; exports.update = update;