M src/App.vue => src/App.vue +34 -2
@@ 2,6 2,7 @@
<div id="app">
<main-menu :visible.sync="navVisible" />
<div class="content">
+ <notifications v-bind="appStatus" @refresh="refresh" />
<router-view />
</div>
</div>
@@ 9,10 10,11 @@
<script>
import MainMenu from '@/components/MainMenu.vue'
+import Notifications from '@/components/Notifications.vue'
export default {
name: 'app',
- components: { MainMenu },
+ components: { MainMenu, Notifications },
watch: {
$route () {
this.navVisible = false
@@ 20,7 22,37 @@ export default {
},
data () {
return {
- navVisible: false
+ navVisible: false,
+ swRegistration: null,
+ appStatus: {
+ hasUpdate: false,
+ isOffline: false,
+ isUpdating: false
+ }
+ }
+ },
+ mounted () {
+ document.addEventListener('swUpdated', registration => {
+ this.swRegistration = registration
+ this.appStatus.hasUpdate = true
+ }, { once: true })
+
+ document.addEventListener('swOffline', registration => {
+ this.appStatus.isOffline = true
+ })
+
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ if (this.appStatus.isUpdating) return
+ this.appStatus.isUpdating = true
+ window.location.reload()
+ })
+ },
+ methods: {
+ refresh () {
+ this.appStatus.hasUpdate = false
+ if (!this.swRegistration || !this.swRegistration.waiting) return
+
+ this.swRegistration.waiting.postMessage('skipWaiting')
}
}
}
A src/components/Notifications.vue => src/components/Notifications.vue +46 -0
@@ 0,0 1,46 @@
+<template>
+ <div id="notifications" :class="{ visible }">
+ <div id="update-notification" v-if="hasUpdate">
+ There's an update available. Please
+ <a href="/" @click.prevent="$emit('refresh')">refresh</a>.
+ </div>
+ <div id="updating-notification" v-if="isUpdating">
+ App is updating...
+ </div>
+ <div id="offline-notification" v-if="isOffline">
+ You are offline. Only cached articles will be available.
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'notifications',
+ props: {
+ hasUpdate: Boolean,
+ isOffline: Boolean,
+ isUpdating: Boolean
+ },
+ computed: {
+ visible () {
+ return this.hasUpdate || this.isOffline || this.isUpdating
+ }
+ }
+}
+</script>
+
+<style scoped>
+#notifications {
+ position: absolute;
+ top: -3em;
+ width: 100%;
+ max-width: calc(70rem - 4em);
+ padding: 1em 2em;
+ background: #FFF3;
+ text-align: center;
+ transition: transform .3s;
+}
+#notifications.visible {
+ transform: translate(0, 3em);
+}
+</style>
M src/registerServiceWorker.js => src/registerServiceWorker.js +12 -3
@@ 10,8 10,11 @@ if (process.env.NODE_ENV === 'production') {
'For more details, visit https://goo.gl/AFskqB'
)
},
- registered () {
+ registered (registration) {
console.log('Service worker has been registered.')
+ setInterval(() => {
+ registration.update()
+ }, 60 * 60 * 1000) // hourly check for updates
},
cached () {
console.log('Content has been cached for offline use.')
@@ 19,11 22,17 @@ if (process.env.NODE_ENV === 'production') {
updatefound () {
console.log('New content is downloading.')
},
- updated () {
+ updated (registration) {
console.log('New content is available; please refresh.')
+ document.dispatchEvent(
+ new CustomEvent('swUpdated', { detail: registration })
+ )
},
- offline () {
+ offline (registration) {
console.log('No internet connection found. App is running in offline mode.')
+ document.dispatchEvent(
+ new CustomEvent('swOffline', { detail: registration })
+ )
},
error (error) {
console.error('Error during service worker registration:', error)
A src/sw.js => src/sw.js +21 -0
@@ 0,0 1,21 @@
+// This is the code piece that GenerateSW mode can't provide for us.
+// This code listens for the user's confirmation to update the app.
+self.addEventListener('message', (e) => {
+ if (!e.data) { return }
+
+ switch (e.data) {
+ case 'skipWaiting':
+ self.skipWaiting()
+ break
+ default:
+ // NOOP
+ break
+ }
+})
+
+workbox.clientsClaim()
+
+// The precaching code provided by Workbox.
+self.__precacheManifest = [].concat(self.__precacheManifest || [])
+workbox.precaching.suppressWarnings()
+workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
A vue.config.js => vue.config.js +9 -0
@@ 0,0 1,9 @@
+module.exports = {
+ pwa: {
+ workboxPluginMode: 'InjectManifest',
+ workboxOptions: {
+ swSrc: './src/sw.js',
+ swDest: 'service-worker.js'
+ }
+ }
+}