Add cache-first service worker

This commit is contained in:
Ken-Håvard Lieng 2018-11-06 11:13:32 +01:00
parent b2b5f82486
commit ca222ff10d
20 changed files with 672 additions and 181 deletions

File diff suppressed because one or more lines are too long

View File

@ -54,13 +54,13 @@
"webpack": "^4.23.1",
"webpack-dev-middleware": "^3.4.0",
"webpack-hot-middleware": "^2.24.3",
"webpack-manifest-plugin": "^2.0.4"
"webpack-manifest-plugin": "^2.0.4",
"workbox-webpack-plugin": "^3.6.3"
},
"dependencies": {
"autolinker": "^1.7.1",
"backo": "^1.1.0",
"classnames": "^2.2.6",
"es6-promise": "^4.2.5",
"fontfaceobserver": "^2.0.9",
"formik": "^1.3.1",
"history": "4.5.1",

View File

@ -186,11 +186,14 @@ i[class*=' icon-']:before {
.app-info {
width: 100%;
font-family: Montserrat, sans-serif;
background: #f6546a;
background: #6bb758;
color: #fff;
height: 50px;
line-height: 50px;
text-align: center;
padding: 15px;
}
.app-info-error {
background: #f6546a;
}
.app-container {
@ -800,10 +803,6 @@ input.message-input-nick.invalid {
}
@media (max-width: 600px) {
.app-info {
font-size: 12px;
}
.tablist {
width: 200px;
transform: translateX(-200px);

14
client/src/js/boot.js Normal file
View File

@ -0,0 +1,14 @@
/* eslint-disable no-underscore-dangle */
// This entrypoint gets inlined in the index page cached by service workers
// and is responsible for fetching the data we would otherwise embed
window.__env__ = fetch('/data', {
credentials: 'same-origin'
}).then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.statusText);
});

View File

@ -1,7 +1,8 @@
import React, { Suspense, lazy } from 'react';
import Route from 'containers/Route';
import AppInfo from 'components/AppInfo';
import TabList from 'components/TabList';
import classnames from 'classnames';
import cn from 'classnames';
const Chat = lazy(() => import('containers/Chat'));
const Connect = lazy(() => import('containers/Connect'));
@ -16,9 +17,10 @@ const App = ({
showTabList,
select,
push,
hideMenu
hideMenu,
newVersionAvailable
}) => {
const mainClass = classnames('main-container', {
const mainClass = cn('main-container', {
'off-canvas': showTabList
});
@ -31,9 +33,15 @@ const App = ({
return (
<div className="wrap" onClick={handleClick}>
{!connected && (
<div className="app-info">
<AppInfo type="error">
Connection lost, attempting to reconnect...
</div>
</AppInfo>
)}
{newVersionAvailable && (
<AppInfo dismissible>
A new version of dispatch just got installed, reload to start using
it!
</AppInfo>
)}
<div className="app-container">
<TabList

View File

@ -0,0 +1,28 @@
import React, { useState } from 'react';
import cn from 'classnames';
const AppInfo = ({ type, children, dismissible }) => {
const [dismissed, setDismissed] = useState(false);
if (!dismissed) {
const handleDismiss = () => {
if (dismissible) {
setDismissed(true);
}
};
const className = cn('app-info', {
[`app-info-${type}`]: type
});
return (
<div className={className} onClick={handleDismiss}>
{children}
</div>
);
}
return null;
};
export default AppInfo;

View File

@ -102,6 +102,7 @@ class Connect extends Component {
}
export default withFormik({
enableReinitialize: true,
mapPropsToValues: ({ defaults }) => {
let port = 6667;
if (defaults.port) {

View File

@ -15,7 +15,8 @@ const mapState = createStructuredSelector({
privateChats: getPrivateChats,
servers: getServers,
showTabList: getShowTabList,
tab: getSelectedTab
tab: getSelectedTab,
newVersionAvailable: state => state.app.newVersionAvailable
});
const mapDispatch = { push, select, hideMenu };

View File

@ -1,14 +1,14 @@
//import 'es6-promise/auto';
//import 'utils/ie11';
import React from 'react';
import { render } from 'react-dom';
import Root from 'components/Root';
import { appSet } from 'state/app';
import initRouter from 'utils/router';
import Socket from 'utils/Socket';
import configureStore from './store';
import routes from './routes';
import runModules from './modules';
import { register } from './serviceWorker';
import '../css/fonts.css';
import '../css/fontello.css';
import '../css/style.css';
@ -24,3 +24,7 @@ initRouter(routes, store);
runModules({ store, socket });
render(<Root store={store} />, document.getElementById('root'));
register({
onUpdate: () => store.dispatch(appSet('newVersionAvailable', true))
});

View File

@ -1,3 +1,4 @@
/* eslint-disable no-underscore-dangle */
import Cookie from 'js-cookie';
import { socket as socketActions } from 'state/actions';
import { getWrapWidth, setConnectDefaults, appSet } from 'state/app';
@ -8,9 +9,7 @@ import { find } from 'utils';
import { when } from 'utils/observe';
import { replace } from 'utils/router';
export default function initialState({ store }) {
const env = JSON.parse(document.getElementById('env').innerHTML);
function loadState({ store }, env) {
store.dispatch(setConnectDefaults(env.defaults));
store.dispatch(appSet('hexIP', env.hexIP));
store.dispatch(setSettings(env.settings, true));
@ -70,3 +69,12 @@ export default function initialState({ store }) {
}
});
}
export default function initialState(ctx) {
if (window.__env__) {
window.__env__.then(env => loadState(ctx, env));
} else {
const env = JSON.parse(document.getElementById('env').innerHTML);
loadState(ctx, env);
}
}

View File

@ -0,0 +1,90 @@
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}$/
)
);
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// 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.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.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, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = '/sw.js';
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@ -22,7 +22,8 @@ const initialState = {
readonly: false,
showDetails: false
},
hexIP: false
hexIP: false,
newVersionAvailable: false
};
export default createReducer(initialState, {

6
client/src/js/sw.js Normal file
View File

@ -0,0 +1,6 @@
workbox.skipWaiting();
workbox.clientsClaim();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {
ignoreUrlParametersMatching: [/.*/]
});

View File

@ -1,12 +0,0 @@
if (Object.keys) {
try {
Object.keys('');
} catch (e) {
Object.keys = function keys(o, k, r) {
r = [];
// eslint-disable-next-line
for (k in o) r.hasOwnProperty.call(o, k) && r.push(k);
return r;
};
}
}

View File

@ -4,10 +4,14 @@ var postcssPresetEnv = require('postcss-preset-env');
var cssnano = require('cssnano');
var TerserPlugin = require('terser-webpack-plugin');
var ManifestPlugin = require('webpack-manifest-plugin');
var { InjectManifest } = require('workbox-webpack-plugin');
module.exports = {
mode: 'production',
entry: ['./src/js/index'],
entry: {
main: './src/js/index',
boot: './src/js/boot'
},
output: {
filename: '[name].[chunkhash:8].js',
chunkFilename: '[name].[chunkhash:8].js'
@ -76,6 +80,11 @@ module.exports = {
}),
new ManifestPlugin({
fileName: 'asset-manifest.json'
}),
new InjectManifest({
swSrc: './src/js/sw.js',
globDirectory: './src',
globPatterns: ['font/*.woff2']
})
],
optimization: {
@ -89,6 +98,6 @@ module.exports = {
}
}
},
runtimeChunk: true
runtimeChunk: 'single'
}
};

View File

@ -1489,6 +1489,13 @@ babel-eslint@^10.0.1:
eslint-scope "3.7.1"
eslint-visitor-keys "^1.0.0"
babel-extract-comments@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21"
integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==
dependencies:
babylon "^6.18.0"
babel-generator@^6.18.0, babel-generator@^6.26.0:
version "6.26.1"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
@ -1551,11 +1558,19 @@ babel-plugin-jest-hoist@^23.2.0:
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
integrity sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=
babel-plugin-syntax-object-rest-spread@^6.13.0:
babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
babel-plugin-transform-object-rest-spread@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=
dependencies:
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.26.0"
babel-preset-jest@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46"
@ -2365,6 +2380,11 @@ commander@~2.17.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
common-tags@^1.4.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -3267,11 +3287,6 @@ es6-promise@^4.1.1:
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
es6-promise@^4.2.5:
version "4.2.5"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
es6-symbol@^3.1.1, es6-symbol@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
@ -4043,6 +4058,15 @@ from2@^2.1.0:
inherits "^2.0.1"
readable-stream "^2.0.0"
fs-extra@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.0.tgz#8cc3f47ce07ef7b3593a11b9fb245f7e34c041d6"
@ -4146,6 +4170,11 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203"
integrity sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
@ -5070,7 +5099,7 @@ is-number@^4.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
is-obj@^1.0.0:
is-obj@^1.0.0, is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
@ -5123,6 +5152,11 @@ is-regex@^1.0.4:
dependencies:
has "^1.0.1"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
is-relative@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
@ -5189,6 +5223,13 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isemail@3.x.x:
version "3.2.0"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.2.0.tgz#59310a021931a9fb06bbb51e155ce0b3f236832c"
integrity sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==
dependencies:
punycode "2.x.x"
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@ -5607,6 +5648,15 @@ jest@^23.6.0:
import-local "^1.0.0"
jest-cli "^23.6.0"
joi@^11.1.1:
version "11.4.0"
resolved "https://registry.yarnpkg.com/joi/-/joi-11.4.0.tgz#f674897537b625e9ac3d0b7e1604c828ad913ccb"
integrity sha512-O7Uw+w/zEWgbL6OcHbyACKSj0PkQeUgmehdoXVSxt92QFCq4+1390Rwh5moI2K/OgC7D8RHRZqHZxT2husMJHA==
dependencies:
hoek "4.x.x"
isemail "3.x.x"
topo "2.x.x"
js-cookie@^2.1.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb"
@ -6044,7 +6094,7 @@ lodash.template@^3.0.0:
lodash.restparam "^3.0.0"
lodash.templatesettings "^3.0.0"
lodash.template@^4.2.4:
lodash.template@^4.2.4, lodash.template@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=
@ -7877,6 +7927,11 @@ prettier@1.14.3:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895"
integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==
pretty-bytes@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=
pretty-format@^23.6.0:
version "23.6.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760"
@ -8017,16 +8072,16 @@ punycode@1.3.2:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@2.x.x, punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@ -8170,7 +8225,7 @@ react-redux@^5.1.0:
react-is "^16.6.0"
react-lifecycles-compat "^3.0.0"
react-test-renderer@16.7.0-alpha.0:
react-test-renderer@^16.7.0-alpha.0:
version "16.7.0-alpha.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.7.0-alpha.0.tgz#f60e888621537cf8301fc154e8e98e59fb9c7050"
integrity sha512-yOJTaUgy7V/Lpmv61g2oJdwEGUiAVJ21mYlHtUniUC8rW0HVpTVMDAVSEqgjlCYDfTwGh3hNTvoYaODJzkQKMw==
@ -9210,6 +9265,15 @@ string_decoder@~0.10.x:
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
stringify-object@^3.2.2:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
dependencies:
get-own-enumerable-property-symbols "^3.0.0"
is-obj "^1.0.1"
is-regexp "^1.0.0"
stringstream@~0.0.4, stringstream@~0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
@ -9241,6 +9305,14 @@ strip-bom@^2.0.0:
dependencies:
is-utf8 "^0.2.0"
strip-comments@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d"
integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==
dependencies:
babel-extract-comments "^1.0.0"
babel-plugin-transform-object-rest-spread "^6.26.0"
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@ -9537,6 +9609,13 @@ to-through@^2.0.0:
dependencies:
through2 "^2.0.3"
topo@2.x.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
integrity sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=
dependencies:
hoek "4.x.x"
tough-cookie@>=2.3.3, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3:
version "2.3.4"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
@ -10154,6 +10233,133 @@ wordwrap@~1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
workbox-background-sync@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-3.6.3.tgz#6609a0fac9eda336a7c52e6aa227ba2ae532ad94"
integrity sha512-ypLo0B6dces4gSpaslmDg5wuoUWrHHVJfFWwl1udvSylLdXvnrfhFfriCS42SNEe5lsZtcNZF27W/SMzBlva7Q==
dependencies:
workbox-core "^3.6.3"
workbox-broadcast-cache-update@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-broadcast-cache-update/-/workbox-broadcast-cache-update-3.6.3.tgz#3f5dff22ada8c93e397fb38c1dc100606a7b92da"
integrity sha512-pJl4lbClQcvp0SyTiEw0zLSsVYE1RDlCPtpKnpMjxFtu8lCFTAEuVyzxp9w7GF4/b3P4h5nyQ+q7V9mIR7YzGg==
dependencies:
workbox-core "^3.6.3"
workbox-build@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-3.6.3.tgz#77110f9f52dc5d82fa6c1c384c6f5e2225adcbd8"
integrity sha512-w0clZ/pVjL8VXy6GfthefxpEXs0T8uiRuopZSFVQ8ovfbH6c6kUpEh6DcYwm/Y6dyWPiCucdyAZotgjz+nRz8g==
dependencies:
babel-runtime "^6.26.0"
common-tags "^1.4.0"
fs-extra "^4.0.2"
glob "^7.1.2"
joi "^11.1.1"
lodash.template "^4.4.0"
pretty-bytes "^4.0.2"
stringify-object "^3.2.2"
strip-comments "^1.0.2"
workbox-background-sync "^3.6.3"
workbox-broadcast-cache-update "^3.6.3"
workbox-cache-expiration "^3.6.3"
workbox-cacheable-response "^3.6.3"
workbox-core "^3.6.3"
workbox-google-analytics "^3.6.3"
workbox-navigation-preload "^3.6.3"
workbox-precaching "^3.6.3"
workbox-range-requests "^3.6.3"
workbox-routing "^3.6.3"
workbox-strategies "^3.6.3"
workbox-streams "^3.6.3"
workbox-sw "^3.6.3"
workbox-cache-expiration@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-cache-expiration/-/workbox-cache-expiration-3.6.3.tgz#4819697254a72098a13f94b594325a28a1e90372"
integrity sha512-+ECNph/6doYx89oopO/UolYdDmQtGUgo8KCgluwBF/RieyA1ZOFKfrSiNjztxOrGJoyBB7raTIOlEEwZ1LaHoA==
dependencies:
workbox-core "^3.6.3"
workbox-cacheable-response@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-3.6.3.tgz#869f1a68fce9063f6869ddbf7fa0a2e0a868b3aa"
integrity sha512-QpmbGA9SLcA7fklBLm06C4zFg577Dt8u3QgLM0eMnnbaVv3rhm4vbmDpBkyTqvgK/Ly8MBDQzlXDtUCswQwqqg==
dependencies:
workbox-core "^3.6.3"
workbox-core@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-3.6.3.tgz#69abba70a4f3f2a5c059295a6f3b7c62bd00e15c"
integrity sha512-cx9cx0nscPkIWs8Pt98HGrS9/aORuUcSkWjG25GqNWdvD/pSe7/5Oh3BKs0fC+rUshCiyLbxW54q0hA+GqZeSQ==
workbox-google-analytics@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-3.6.3.tgz#99df2a3d70d6e91961e18a6752bac12e91fbf727"
integrity sha512-RQBUo/6SXtIaQTRFj4RQZ9e1gAl7D8oS5S+Hi173Kk70/BgJjzPwXpC5A249Jv5YfkCOLMQCeF9A27BiD0b0ig==
dependencies:
workbox-background-sync "^3.6.3"
workbox-core "^3.6.3"
workbox-routing "^3.6.3"
workbox-strategies "^3.6.3"
workbox-navigation-preload@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-3.6.3.tgz#a2c34eb7c17e7485b795125091215f757b3c4964"
integrity sha512-dd26xTX16DUu0i+MhqZK/jQXgfIitu0yATM4jhRXEmpMqQ4MxEeNvl2CgjDMOHBnCVMax+CFZQWwxMx/X/PqCw==
dependencies:
workbox-core "^3.6.3"
workbox-precaching@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-3.6.3.tgz#5341515e9d5872c58ede026a31e19bafafa4e1c1"
integrity sha512-aBqT66BuMFviPTW6IpccZZHzpA8xzvZU2OM1AdhmSlYDXOJyb1+Z6blVD7z2Q8VNtV1UVwQIdImIX+hH3C3PIw==
dependencies:
workbox-core "^3.6.3"
workbox-range-requests@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-3.6.3.tgz#3cc21cba31f2dd8c43c52a196bcc8f6cdbcde803"
integrity sha512-R+yLWQy7D9aRF9yJ3QzwYnGFnGDhMUij4jVBUVtkl67oaVoP1ymZ81AfCmfZro2kpPRI+vmNMfxxW531cqdx8A==
dependencies:
workbox-core "^3.6.3"
workbox-routing@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-3.6.3.tgz#659cd8f9274986cfa98fda0d050de6422075acf7"
integrity sha512-bX20i95OKXXQovXhFOViOK63HYmXvsIwZXKWbSpVeKToxMrp0G/6LZXnhg82ijj/S5yhKNRf9LeGDzaqxzAwMQ==
dependencies:
workbox-core "^3.6.3"
workbox-strategies@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-3.6.3.tgz#11a0dc249a7bc23d3465ec1322d28fa6643d64a0"
integrity sha512-Pg5eulqeKet2y8j73Yw6xTgLdElktcWExGkzDVCGqfV9JCvnGuEpz5eVsCIK70+k4oJcBCin9qEg3g3CwEIH3g==
dependencies:
workbox-core "^3.6.3"
workbox-streams@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-3.6.3.tgz#beaea5d5b230239836cc327b07d471aa6101955a"
integrity sha512-rqDuS4duj+3aZUYI1LsrD2t9hHOjwPqnUIfrXSOxSVjVn83W2MisDF2Bj+dFUZv4GalL9xqErcFW++9gH+Z27w==
dependencies:
workbox-core "^3.6.3"
workbox-sw@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-3.6.3.tgz#278ea4c1831b92bbe2d420da8399176c4b2789ff"
integrity sha512-IQOUi+RLhvYCiv80RP23KBW/NTtIvzvjex28B8NW1jOm+iV4VIu3VXKXTA6er5/wjjuhmtB28qEAUqADLAyOSg==
workbox-webpack-plugin@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-3.6.3.tgz#a807bb891b4e4e3c808df07e58f17de2d5ba6182"
integrity sha512-RwmKjc7HFHUFHoOlKoZUq9349u0QN3F8W5tZZU0vc1qsBZDINWXRiIBCAKvo/Njgay5sWz7z4I2adnyTo97qIQ==
dependencies:
babel-runtime "^6.26.0"
json-stable-stringify "^1.0.1"
workbox-build "^3.6.3"
worker-farm@^1.5.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"

View File

@ -21,6 +21,7 @@
<% if cssPath != "" { %>
<link href="/<%== cssPath %>" rel="stylesheet">
<% } %>
<link rel="icon" href="data:;base64,=">
<script><%== inlineScript %></script>
@ -28,7 +29,11 @@
<body>
<div id="root"></div>
<% if data != nil { %>
<script id="env" type="application/json"><% easyjson.MarshalToWriter(data, w) %></script>
<% } %>
<% for _, script := range scripts { %>
<script src="/<%== script %>"></script>
<% } %>

View File

@ -16,9 +16,12 @@ io.WriteString(w, "\" rel=\"stylesheet\">")
}
io.WriteString(w, "<link rel=\"icon\" href=\"data:;base64,=\"><script>")
io.WriteString(w, inlineScript )
io.WriteString(w, "</script></head><body><div id=\"root\"></div><script id=\"env\" type=\"application/json\">")
io.WriteString(w, "</script></head><body><div id=\"root\"></div>")
if data != nil {
io.WriteString(w, "<script id=\"env\" type=\"application/json\">")
easyjson.MarshalToWriter(data, w)
io.WriteString(w, "</script>")
}
for _, script := range scripts {
io.WriteString(w, "<script src=\"/")
io.WriteString(w, script )

View File

@ -49,10 +49,13 @@ func newH2PushAsset(name string) h2PushAsset {
var (
files []*File
indexStylesheet string
indexScripts []string
inlineScript string
inlineScriptSha256 string
indexStylesheet string
indexScripts []string
inlineScript string
inlineScriptSha256 string
inlineScriptSW string
inlineScriptSWSha256 string
serviceWorker []byte
h2PushAssets []h2PushAsset
h2PushCookieValue string
@ -84,17 +87,21 @@ func (d *Dispatch) initFileServer() {
log.Fatal(err)
}
runtime, err := assets.Asset(manifest["runtime~main.js"] + ".br")
if err != nil {
log.Fatal(err)
}
runtime = decompressAsset(runtime)
bootloader := decompressedAsset(manifest["boot.js"])
runtime := decompressedAsset(manifest["runtime.js"])
inlineScript = string(runtime)
inlineScriptSW = string(bootloader) + string(runtime)
hash := sha256.New()
hash.Write(runtime)
inlineScriptSha256 = base64.StdEncoding.EncodeToString(hash.Sum(nil))
hash.Reset()
hash.Write(bootloader)
hash.Write(runtime)
inlineScriptSWSha256 = base64.StdEncoding.EncodeToString(hash.Sum(nil))
indexStylesheet = manifest["main.css"]
indexScripts = []string{
manifest["vendors~main.js"],
@ -111,7 +118,19 @@ func (d *Dispatch) initFileServer() {
h2PushCookieValue += asset.hash
}
ignoreAssets := []string{
manifest["runtime.js"],
manifest["boot.js"],
"sw.js",
}
for _, assetPath := range manifest {
for _, ignored := range ignoreAssets {
if assetPath == ignored {
continue
}
}
file := &File{
Path: assetPath,
Asset: assetPath + ".br",
@ -153,6 +172,18 @@ func (d *Dispatch) initFileServer() {
}
}
serviceWorker = decompressedAsset("sw.js")
hash.Reset()
IndexTemplate(hash, nil, indexStylesheet, inlineScriptSW, indexScripts)
indexHash := base64.StdEncoding.EncodeToString(hash.Sum(nil))
serviceWorker = append(serviceWorker, []byte(`
workbox.precaching.precacheAndRoute([{
revision: '`+indexHash+`',
url: '/?sw'
}]);
workbox.routing.registerNavigationRoute('/?sw');`)...)
if viper.GetBool("https.hsts.enabled") && viper.GetBool("https.enabled") {
hstsHeader = "max-age=" + viper.GetString("https.hsts.max_age")
@ -179,6 +210,14 @@ func decompressAsset(data []byte) []byte {
return buf.Bytes()
}
func decompressedAsset(name string) []byte {
asset, err := assets.Asset(name + ".br")
if err != nil {
log.Fatal(err)
}
return decompressAsset(asset)
}
func gzipAsset(data []byte) []byte {
br, err := brotli.NewReader(bytes.NewReader(data), nil)
if err != nil {
@ -202,6 +241,14 @@ func (d *Dispatch) serveFiles(w http.ResponseWriter, r *http.Request) {
return
}
if r.URL.Path == "/sw.js" {
w.Header().Set("Cache-Control", disabledCacheControl)
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Content-Length", strconv.Itoa(len(serviceWorker)))
w.Write(serviceWorker)
return
}
for _, file := range files {
if strings.HasSuffix(r.URL.Path, file.Path) {
d.serveFile(w, r, file)
@ -215,15 +262,22 @@ func (d *Dispatch) serveFiles(w http.ResponseWriter, r *http.Request) {
func (d *Dispatch) serveIndex(w http.ResponseWriter, r *http.Request) {
state := d.handleAuth(w, r, false)
_, sw := r.URL.Query()["sw"]
if cspEnabled {
var connectSrc string
var wsSrc string
if r.TLS != nil {
connectSrc = "wss://" + r.Host
wsSrc = "wss://" + r.Host
} else {
connectSrc = "ws://" + r.Host
wsSrc = "ws://" + r.Host
}
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self' 'sha256-"+inlineScriptSha256+"'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src data:; connect-src "+connectSrc)
inlineSha := inlineScriptSha256
if sw {
inlineSha = inlineScriptSWSha256
}
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self' 'sha256-"+inlineSha+"'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src data:; connect-src 'self' "+wsSrc)
}
w.Header().Set("Content-Type", "text/html")
@ -266,14 +320,24 @@ func (d *Dispatch) serveIndex(w http.ResponseWriter, r *http.Request) {
}
}
var data *indexData
if !sw {
data = getIndexData(r, state)
}
inline := inlineScript
if sw {
inline = inlineScriptSW
}
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
gzw := gzip.NewWriter(w)
IndexTemplate(gzw, getIndexData(r, state), indexStylesheet, inlineScript, indexScripts)
IndexTemplate(gzw, data, indexStylesheet, inline, indexScripts)
gzw.Close()
} else {
IndexTemplate(w, getIndexData(r, state), indexStylesheet, inlineScript, indexScripts)
IndexTemplate(w, data, indexStylesheet, inline, indexScripts)
}
}
@ -289,16 +353,6 @@ func setPushCookie(w http.ResponseWriter, r *http.Request) {
}
func (d *Dispatch) serveFile(w http.ResponseWriter, r *http.Request, file *File) {
info, err := assets.AssetInfo(file.Asset)
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
return
}
if !modifiedSince(w, r, info.ModTime()) {
return
}
data, err := assets.Asset(file.Asset)
if err != nil {
http.Error(w, "", http.StatusInternalServerError)
@ -334,15 +388,3 @@ func (d *Dispatch) serveFile(w http.ResponseWriter, r *http.Request, file *File)
w.Write(buf)
}
}
func modifiedSince(w http.ResponseWriter, r *http.Request, modtime time.Time) bool {
t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since"))
if err == nil && modtime.Before(t.Add(1*time.Second)) {
w.WriteHeader(http.StatusNotModified)
return false
}
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
return true
}

View File

@ -10,11 +10,11 @@ import (
"strings"
"github.com/gorilla/websocket"
"github.com/spf13/viper"
"github.com/khlieng/dispatch/pkg/letsencrypt"
"github.com/khlieng/dispatch/pkg/session"
"github.com/khlieng/dispatch/storage"
"github.com/mailru/easyjson"
"github.com/spf13/viper"
)
var channelStore = storage.NewChannelStore()
@ -181,6 +181,15 @@ func (d *Dispatch) serve(w http.ResponseWriter, r *http.Request) {
}
d.upgradeWS(w, r, state)
} else if strings.HasPrefix(r.URL.Path, "/data") {
state := d.handleAuth(w, r, true)
if state == nil {
log.Println("[Auth] No state")
fail(w, http.StatusInternalServerError)
return
}
easyjson.MarshalToHTTPResponseWriter(getIndexData(r, state), w)
} else {
d.serveFiles(w, r)
}