Commit 546ee664 authored by Andrew Hrdy's avatar Andrew Hrdy

Start work on service worker

parent c641d8e2
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -2,8 +2,6 @@ import * as React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { IFacility } from '../models/facility.model';
import { ApplicationState } from '../store';
import { fetchFacilitiesAction } from '../store/facility/facility.actions';
import { fetchAlertsAction } from '../store/alert/alert.actions';
import { filterFacilities } from '../utils/facility.util';
import CardContainer from '../components/CardContainer';
......@@ -21,10 +19,6 @@ export default () => {
const isFetching = useSelector((state: ApplicationState) => state.facilities.isFetching);
const dispatch = useDispatch();
const fetch = () => {
dispatch(fetchFacilitiesAction());
dispatch(fetchAlertsAction());
};
const selectFacility = (slug: string) => dispatch(setSelectedFacilityAction(slug));
const selectedFacility = facilities.find(f => f.slug === selectedFacilitySlug);
......@@ -35,10 +29,6 @@ export default () => {
setSidebarFacility(selectedFacility);
}
React.useEffect(() => {
fetch();
}, []);
const closeSidebar = () => {
selectFacility('');
};
......
import * as React from 'react';
import { IFacility } from '../models/facility.model';
import { ApplicationState } from '../store';
import { fetchFacilitiesAction } from '../store/facility/facility.actions';
import { fetchAlertsAction } from '../store/alert/alert.actions';
import { useDispatch, useSelector } from 'react-redux';
import { filterFacilities } from '../utils/facility.util';
import AppBar from '../components/AppBar';
......@@ -22,10 +20,6 @@ export default () => {
const isFetching = useSelector((state: ApplicationState) => state.facilities.isFetching);
const dispatch = useDispatch();
const fetch = () => {
dispatch(fetchFacilitiesAction());
dispatch(fetchAlertsAction());
};
const selectFacility = (slug: string) => dispatch(setSelectedFacilityAction(slug));
const selectedFacility = facilities.find(f => f.slug === selectedFacilitySlug);
......@@ -36,10 +30,6 @@ export default () => {
setDrawerFacility(selectedFacility);
}
React.useEffect(() => {
fetch();
}, []);
const closeDrawer = () => {
selectFacility('');
};
......
......@@ -2,7 +2,6 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import DesktopLayout from './containers/DesktopLayout';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import './styles/whatsOpen.scss';
......@@ -15,6 +14,9 @@ import { themeTypography, themeOverrides } from './theme';
import { isMobile } from 'react-device-detect';
import MobileLayout from './containers/MobileLayout';
import { useMediaQuery } from '@material-ui/core';
import { fetchFacilitiesAction, setFacilitiesAction } from './store/facility/facility.actions';
import { fetchAlertsAction } from './store/alert/alert.actions';
import { IFacility } from './models/facility.model';
if (isMobile) {
require('./styles/whatsOpenMobile.scss');
......@@ -38,9 +40,34 @@ const App = () => {
);
};
const store = generateStore();
// Event listener must be before the fetch calls
navigator.serviceWorker.addEventListener('message', async(event: any) => {
if (event.meta === 'workbox-broadcast-update') {
const { cacheName, updatedUrl } = event.data.payload;
const cache = await caches.open(cacheName);
const resp = await cache.match(updatedUrl);
const updatedFacilities: IFacility[] = await resp.json();
store.dispatch(setFacilitiesAction(updatedFacilities));
}
});
// Fetch facilities and alerts before app loads.
store.dispatch(fetchFacilitiesAction());
store.dispatch(fetchAlertsAction());
ReactDOM.render(
<Provider store={generateStore()}>
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
registerServiceWorker();
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// This is the path on the server, not path in src.
navigator.serviceWorker.register('/sw.js');
});
}
// tslint:disable:no-console
// 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.toString()
);
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) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker) {
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: string) {
// 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) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import { setFacilitiesAction } from './facility.actions';
import { FacilityActionTypes } from './facility.action-types';
import { IFacility } from '../../models/facility.model';
import { isFacilityOpen } from '../../utils/facility.util';
import { ApplicationState, LOCAL_STORAGE_FACILITIES_KEY } from '..';
import { ApplicationState } from '..';
const API_GET_FACILITIES = process.env.API_GET_FACILITIES || 'https://api.srct.gmu.edu/whatsopen/v2/facilities/';
......@@ -40,8 +40,6 @@ function* handleFetchFacilities() {
const facilities = res.sort(facilitySort(favorites));
yield put(setFacilitiesAction(facilities));
localStorage.setItem(LOCAL_STORAGE_FACILITIES_KEY, JSON.stringify(facilities));
} catch (err) {}
}
......
......@@ -14,7 +14,6 @@ import { FacilityAction } from './facility/facility.action-types';
import { UiAction } from './ui/ui.action-types';
import uiSaga from './ui/ui.saga';
export const LOCAL_STORAGE_FACILITIES_KEY = 'facilities';
export const LOCAL_STORAGE_VIEWED_ALERTS_KEY = 'viewedAlerts';
export const LOCAL_STORAGE_FAVORITES_KEY = 'favorites';
......@@ -46,7 +45,7 @@ export const generateStore = (): Store<ApplicationState, ApplicationAction> => {
let viewedAlerts: number[];
try {
facilities = JSON.parse(localStorage.getItem(LOCAL_STORAGE_FACILITIES_KEY)) || [];
facilities = [];
favorites = JSON.parse(localStorage.getItem(LOCAL_STORAGE_FAVORITES_KEY)) || [];
viewedAlerts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_VIEWED_ALERTS_KEY)) || [];
} catch (ex) {
......
import { skipWaiting } from 'workbox-core/skipWaiting';
import { clientsClaim } from 'workbox-core/clientsClaim';
import { registerRoute } from 'workbox-routing/registerRoute';
import { StaleWhileRevalidate } from 'workbox-strategies/StaleWhileRevalidate';
import { precacheAndRoute } from 'workbox-precaching/precacheAndRoute';
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update/BroadcastUpdatePlugin';
skipWaiting();
clientsClaim();
precacheAndRoute((self as any).__WB_MANIFEST || []);
registerRoute(
process.env.API_GET_FACILITIES || 'https://api.srct.gmu.edu/whatsopen/v2/facilities/',
new StaleWhileRevalidate({
cacheName: 'facilities',
plugins: [
new BroadcastUpdatePlugin({})
]
})
);
registerRoute(
/.*\.(?:png|jpg|jpeg|svg|gif)/,
new StaleWhileRevalidate({
cacheName: 'images'
})
);
\ No newline at end of file
......@@ -10,6 +10,7 @@ module.exports = {
appIndex: resolveApp('src/index.tsx'),
appBuild: resolveApp('build'),
appSrc: resolveApp('src'),
appSw: resolveApp('src/sw.ts'),
appOutputJs: 'static/js/bundle.js',
publicPath: '/'
};
\ No newline at end of file
......@@ -2,6 +2,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const paths = require('./paths');
const loaders = require('./loaders');
const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
mode: 'development',
......@@ -31,6 +32,11 @@ module.exports = {
inject: true,
template: paths.appHtml
}),
new WorkboxPlugin.InjectManifest({
swSrc: paths.appSw,
swDest: 'sw.js',
maximumFileSizeToCacheInBytes: 1024 * 1024 * 10 // Dev builds are not minified, allow large files (10MB) for the bundle
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
......
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const paths = require('./paths');
const loaders = require('./loaders');
......@@ -33,15 +33,10 @@ module.exports = {
]
},
optimization: {
usedExports: true,
minimize: true,
minimizer: [
new UglifyJsPlugin({
parallel: true,
uglifyOptions: {
compress: {
typeofs: false // Causes issues with mapbox.
}
}
}),
new TerserPlugin(),
new OptimizeCSSAssetsPlugin({})
]
},
......@@ -63,22 +58,9 @@ module.exports = {
minifyURLs: true
}
}),
new SWPrecacheWebpackPlugin({
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js',
logger(message) {
if (message.indexOf('Total precache size is') === 0) {
return;
}
if (message.indexOf('Skipping static resource') === 0) {
return;
}
console.log(message);
},
minify: true,
navigateFallback: `${paths.publicUrl}/index.html`,
navigateFallbackWhitelist: [/^(?!\/__).*/],
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/]
new WorkboxPlugin.InjectManifest({
swSrc: paths.appSw,
swDest: 'sw.js'
}),
new MiniCssExtractPlugin({
filename: '[name].css',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment