From c7dedb56fddce2ce60db1a12a8caa2519e8bfe36 Mon Sep 17 00:00:00 2001 From: Alexey Kasyanchuk Date: Sat, 28 Oct 2017 02:29:00 +0300 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D1=80=D0=BA=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/webpack.config.prod.js | 29 +++++++++ package.json | 1 + src/app.js | 7 ++- src/registerServiceWorker.js | 113 ++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 src/registerServiceWorker.js diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js index 5dfc580..99a5501 100644 --- a/config/webpack.config.prod.js +++ b/config/webpack.config.prod.js @@ -4,6 +4,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var ManifestPlugin = require('webpack-manifest-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +var SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); var url = require('url'); var paths = require('./paths'); var getClientEnvironment = require('./env'); @@ -236,6 +237,34 @@ module.exports = { new ManifestPlugin({ fileName: 'asset-manifest.json' }), + new SWPrecacheWebpackPlugin({ + // By default, a cache-busting query parameter is appended to requests + // used to populate the caches, to ensure the responses are fresh. + // If a URL is already hashed by Webpack, then there is no concern + // about it being stale, and the cache-busting can be skipped. + dontCacheBustUrlsMatching: /\.\w{8}\./, + filename: 'service-worker.js', + logger(message) { + if (message.indexOf('Total precache size is') === 0) { + // This message occurs for every build and is a bit too noisy. + return; + } + if (message.indexOf('Skipping static resource') === 0) { + // This message obscures real errors so we ignore it. + // https://github.com/facebookincubator/create-react-app/issues/2612 + return; + } + console.log(message); + }, + minify: true, + // For unknown URLs, fallback to the index page + navigateFallback: publicUrl + '/index.html', + // Ignores URLs starting from /__ (useful for Firebase): + // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219 + navigateFallbackWhitelist: [/^(?!\/__).*/], + // Don't precache sourcemaps (they're large) and build asset manifest: + staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], + }), new RobotsPlugin({sitemap: 'http://ratsontheboat.org/sitemap.xml'}) ], // Some libraries import Node modules but don't use them in the browser. diff --git a/package.json b/package.json index 2f0be8b..cab8a80 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "webpack": "1.14.0", "webpack-dev-server": "1.16.2", "webpack-manifest-plugin": "1.1.0", + "sw-precache-webpack-plugin": "0.11.4", "whatwg-fetch": "1.0.0" }, "dependencies": { diff --git a/src/app.js b/src/app.js index ed733c6..9557d0f 100644 --- a/src/app.js +++ b/src/app.js @@ -2,17 +2,18 @@ import React, { Component } from 'react'; import './app.css'; import './router'; import PagesPie from './pages-pie.js'; +import registerServiceWorker from './registerServiceWorker'; +import injectTapEventPlugin from 'react-tap-event-plugin'; +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; var io = require("socket.io-client"); window.torrentSocket = io(document.location.protocol + '//' + document.location.hostname + (process.env.NODE_ENV === 'production' ? '/' : ':8095/')); - -import injectTapEventPlugin from 'react-tap-event-plugin'; // Needed for onTouchTap // http://stackoverflow.com/a/34015469/988941 injectTapEventPlugin(); -import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; +registerServiceWorker(); let loadersCount = 0; let appReady = false; diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js new file mode 100644 index 0000000..a866079 --- /dev/null +++ b/src/registerServiceWorker.js @@ -0,0 +1,113 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (!isLocalhost) { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } else { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + return navigator.serviceWorker.ready.then(registration => { + console.log("Unregister service worker"); + return registration.unregister(); + }); + } else { + return new Promise(function(resolve, reject) { + resolve(); + }) + } +}