Skip to content

Commit 7fedbbd

Browse files
author
Karen Baney
committed
more changes. Still bug with stuck service worker on manual update
1 parent a30f503 commit 7fedbbd

File tree

7 files changed

+259
-55
lines changed

7 files changed

+259
-55
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ How is this different from the vue-cli-plugin-pwa?
66
- vue-cli-plugin-pwa depends on register-service-worker but this does not. The register-service-worker makes several assumptions about how the PWA should work, but this does not meet advanced use cases like cached apis, cache first, etc.
77
- vue-cli-plugin-pwa does not provide a way to easily debug the service worker. This plugin does. It makes use of a new environment variable to enable NODE_ENV=development and injecting the manifest with customizations.
88
- We started with some of the manifest features of vue-cli-plugin-pwa. Because this is a more fully featured pwa, we start with the assumption that InjectManifest mode of Workbox is needed.
9+
- There is **no HMR** when running the pwalocalserve mode because the purpose of this mode is strictly for service worker debugging and not application changes.This means if you make changes to the service worker code (or application) while running in pwalocalserve, you will need to stop the app and re-run it and refresh the browser. For application changes, continue using development mode.
910

1011
## Set up
1112
The scripts commands expects a global install of `npm serve`.
@@ -203,5 +204,16 @@ vue add workbox-pwa
203204
- Manual Update - provides a prompt to the user to manually update the app when a new version of the service worker is available. Depending on your caching strategy, you may want to add code to handle things like flushing the cache, unsaved updates (especially if using Offline Cache), etc.
204205
205206
207+
## Using pwalocalserve
208+
209+
1. Run `npm run pwa-serve`
210+
2. Navigate to the url. Notice in the console, you should see workbox messages. The following browser console messages let you know you are in the pwalocalserve mode.
211+
```
212+
PWA Local Serve: true
213+
Node Env: development
214+
```
215+
3. If you make changes to the service worker code in `src/sw.js` or `src/service-worker/register-service-worker.js`, you will need to stop the application and re-run `npm run pwa-serve`, then refresh the browser. In Chrome Dev Tools under the Application Tab >> Service Worker, you should see there is a new version of the service worker waiting to be activated. Depending on what features you've enabled, the service worker could prompt the user (manual update). You can, of course, customize this code to what you need.
216+
217+
206218
## License
207219
MIT
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script>
2+
import { useServiceWorker } from '@/composables/use-service-worker'
3+
4+
export default {
5+
name: 'AppAutoUpdate',
6+
setup () {
7+
// useServiceWorker has a param to force update so all we have to do is reference this component somewhere, ideally on a login page.
8+
const { updateExists } = useServiceWorker(true)
9+
10+
return {
11+
updateExists
12+
}
13+
}
14+
}
15+
</script>
16+
17+
<template>
18+
<div
19+
v-if="updateExists"
20+
class="warning-msg"
21+
>
22+
<span> Please wait while we update the application.</span>
23+
</div>
24+
</template>
25+
<style scoped>
26+
.warning-msg {
27+
background-color: lightgoldenrodyellow;
28+
border: solid 2px orange;
29+
border-radius: 2px;
30+
margin-bottom: 1rem;
31+
padding: 1rem;
32+
}
33+
</style>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
import { useServiceWorker } from '@/composables/use-service-worker'
3+
4+
export default {
5+
name: 'AppManualUpdate',
6+
setup () {
7+
const { updateExists, refreshApp } = useServiceWorker()
8+
9+
return {
10+
refreshApp,
11+
updateExists
12+
}
13+
}
14+
}
15+
</script>
16+
17+
<template>
18+
<div
19+
style="background-color: #FFFF99;"
20+
v-if="updateExists"
21+
>
22+
Update available
23+
<button @click="refreshApp">
24+
Update
25+
</button>
26+
</div>
27+
</template>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* eslint-disable no-console */
2+
import { ref } from 'vue'
3+
4+
export const useServiceWorker = (forceUpdate = false) => {
5+
let registration = null
6+
const updateExists = ref(false)
7+
const refreshing = ref(false)
8+
9+
const reloadApp = () => {
10+
if (refreshing.value) {
11+
console.log('useServiceWorker: Service Worker already refreshing')
12+
return
13+
}
14+
15+
refreshing.value = true
16+
window.location.reload()
17+
}
18+
19+
const refreshApp = () => {
20+
console.log('useServiceWorker: refreshApp called.')
21+
updateExists.value = false
22+
if (registration) {
23+
const swState = registration.waiting.state
24+
if (swState === 'waiting' || swState === 'installed') {
25+
navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' })
26+
console.log('useServiceWorker: Posted SKIP_WAITING message.')
27+
}
28+
}
29+
}
30+
31+
const updateAvailable = (event) => {
32+
console.log('useServiceWorker: Service worker update available.')
33+
if (event && event.detail) {
34+
registration = event.detail
35+
updateExists.value = true
36+
if (forceUpdate) {
37+
console.log('useServiceWorker: Forcing service worker update.')
38+
refreshApp()
39+
}
40+
}
41+
}
42+
43+
// listen for service worker updates.
44+
document.addEventListener('swUpdated', updateAvailable, { once: true })
45+
46+
if (navigator.serviceWorker) {
47+
navigator.serviceWorker.addEventListener('controllerchange', () => {
48+
console.log('useServiceWorker: controllerchange called')
49+
reloadApp()
50+
})
51+
}
52+
53+
return {
54+
refreshApp,
55+
updateExists
56+
}
57+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script>
2+
import AppAutoUpdate from '@/components/app-auto-update.vue'
3+
4+
export default {
5+
// To auto update your PWA when a new service worker is available, just drop in the AppAutoUpdate component
6+
// The ideal use case for this would be on a login page, if your application has one.
7+
// Some things to note:
8+
// 1. You really only want to auto update when you are sure there will be no adverse affects to your application, like on a login page.
9+
// 2. If you are making your website PWA accessible, then it's probably safe to add this to App.vue or your home page.
10+
// 3. If you are using advanced features of a PWA, like API caching, offline capabilities, etc. You may not want to use the auto update. Instead,
11+
// use the manual update, which gives the user control.
12+
name: 'auto-update-example',
13+
components: {
14+
AppAutoUpdate
15+
}
16+
}
17+
</script>
18+
19+
<template>
20+
<AppAutoUpdate />
21+
</template>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
import AppManualUpdate from '@/components/app-manual-update.vue'
3+
4+
export default {
5+
// Ideally, you'll want to place the AppManualUpdate component inside the App.vue
6+
components: {
7+
AppManualUpdate
8+
}
9+
}
10+
</script>
11+
12+
<template>
13+
<!-- Sample App.vue html -->
14+
<AppManualUpdate />
15+
<div id="nav">
16+
<router-link to="/">Home</router-link> |
17+
<router-link to="/about">About</router-link>
18+
</div>
19+
</template>
Lines changed: 90 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,100 @@
1-
import { Workbox } from 'workbox-window'
1+
/* eslint-disable no-console */
2+
/**
3+
* See workbox documentation for details about the lifecyle events of a service worker.
4+
* https://developers.google.com/web/tools/workbox/modules/workbox-window#important_service_worker_lifecycle_moments
5+
*/
6+
import { Workbox } from 'workbox-window'
27

3-
// const autoUpdate = async (registration: ServiceWorkerRegistration | undefined) => {
4-
// setInterval(async () => {
5-
// try {
6-
// /* eslint-disable-next-line no-unused-expressions */
7-
// await registration?.update()
8-
// } catch (err) {
9-
// /* eslint-disable-next-line no-console */
10-
// console.log(`sw: registration autoUpdate failed: ${err}`)
11-
// }
12-
// }, 1000 * 60 * 60)
13-
// }
8+
const autoUpdate = async (registration) => {
9+
// const updateInterval = 1000 * 60 * 60 // 1 hour
10+
const updateInterval = 100 * 60 * 60 // 1 min // for debugging
11+
setInterval(async () => {
12+
try {
13+
/* eslint-disable-next-line no-unused-expressions */
14+
await registration?.update()
15+
console.log('sw: registration checked for updates')
16+
} catch (err) {
17+
console.log(`sw: registration failed to check for updates with errors: ${err}`)
18+
}
19+
}, updateInterval)
20+
}
1421

15-
// const manualUpdateAvailable = (registration: ServiceWorkerRegistration | undefined) => {
16-
// // Wires up an event that we can listen to in the app. Example: listen for available update and prompt user to update.
17-
// document.dispatchEvent(
18-
// new CustomEvent('swUpdated', { detail: registration }))
19-
// }
22+
const manualUpdateAvailable = (registration) => {
23+
// Wires up an event that we can listen to in the app. Example: listen for available update and prompt user to update.
24+
document.dispatchEvent(
25+
new CustomEvent('swUpdated', { detail: registration }))
26+
}
2027

21-
const register = async () => {
22-
if ('serviceWorker' in navigator) {
23-
// Workbox combines the ./src/sw.js file and injected manifest into the servicer-worker.js file in /dist
24-
// Uses vue.config.js workboxOptions.swSrc for the location of sw.js and swDest for the output location of 'service-worker.js'.
25-
// You can override the file names and locations by changing the values of workboxOptions in vue.config.js.
26-
const wb = new Workbox(`${process.env.BASE_URL}service-worker.js`)
28+
const register = async () => {
29+
if ('serviceWorker' in navigator) {
30+
// Workbox combines the ./src/sw.js file and injected manifest into the servicer-worker.js file in /dist
31+
// Uses vue.config.js workboxOptions.swSrc for the location of sw.js and swDest for the output location of 'service-worker.js'.
32+
// You can override the file names and locations by changing the values of workboxOptions in vue.config.js.
33+
const wb = new Workbox(`${process.env.BASE_URL}service-worker.js`)
2734

28-
// wire up instance of registration so we can take further action on it.
29-
const registration = await wb.register()
30-
console.log(`sw: waiting: ${registration.waiting}`)
35+
// wire up instance of registration so we can take further action on it.
36+
const registration = await wb.register()
37+
console.log('sw: registered')
3138

32-
// autoUpdate(registration)
39+
autoUpdate(registration)
3340

34-
// wb.addEventListener('activated', async (event) => {
35-
// if (event.isUpdate) {
36-
// // event.isUpdate=true means the service worker was already registered and there is a new version available.
41+
wb.addEventListener('activated', async (event) => {
42+
// if (event.isUpdate) {
43+
// event.isUpdate=true means the service worker was already registered and there is a new version available.
3744

38-
// // this only triggers self.skipWaiting. It still doesn't force the app to update. See /composables/use-service-worker.ts for updating app.
39-
// wb.messageSkipWaiting()
45+
// this only triggers self.skipWaiting. It still doesn't force the app to update. See /composables/use-service-worker.ts for updating app.
46+
wb.messageSkipWaiting()
47+
// } else {
48+
// // first time use when event.isUpdate = false
49+
// // service worker should claim the client immediately since its the first install.
50+
// wb.messageSW({ type: 'CLIENTS_CLAIM' })
51+
// console.log('sw: clientsClaim called.')
52+
// }
53+
})
4054

41-
// // Wires up an event that we can listen to in the app for manual updates. Example: listen for available update and prompt user to update.
42-
// manualUpdateAvailable(registration)
43-
// } else {
44-
// // first time use when event.isUpdate = false
45-
// // service worker should claim the client immediately since its the first install.
46-
// wb.messageSW({ type: 'CLIENTS_CLAIM' })
47-
// /* eslint-disable-next-line no-console */
48-
// console.log('sw: clientsClaim called.')
49-
// }
50-
// })
55+
// This code listens for the user's confirmation to update the app.
56+
wb.addEventListener('message', (event) => {
57+
console.log('sw: message event listener hit.')
58+
if (event.data && event.data.type === 'SKIP_WAITING') {
59+
wb.messageSkipWaiting()
60+
console.log('sw: message SKIP_WAITING called.')
61+
}
62+
})
5163

52-
// // This is the code piece that GenerateSW mode can't provide for us.
53-
// // This code listens for the user's confirmation to update the app.
54-
// wb.addEventListener('message', (event) => {
55-
// /* eslint-disable-next-line no-console */
56-
// console.log('sw: message event listener hit.')
57-
// if (event.data && event.data.type === 'SKIP_WAITING') {
58-
// /* eslint-disable-next-line no-console */
59-
// console.log('sw: message SKIP_WAITING called.')
60-
// }
61-
// })
62-
}
63-
}
64+
wb.addEventListener('installed', (event) => {
65+
console.log('sw: installed event listener hit.')
66+
if (event.isUpdate) {
67+
// Wires up event that is listened to in the use-service-worker composable. The app-manual-update component then
68+
// prompts the user there is an update available to activate.
69+
// manualUpdateAvailable(registration)
70+
wb.messageSkipWaiting()
71+
console.log('sw: installed new version.')
72+
} else {
73+
// first time use when event.isUpdate = false
74+
// service worker should claim the client immediately since its the first install.
75+
wb.messageSW({ type: 'CLIENTS_CLAIM' })
76+
console.log('sw: clientsClaim called.')
77+
}
78+
})
6479

65-
export default register
80+
wb.addEventListener('waiting', (event) => {
81+
console.log('sw: waiting event listener hit.')
82+
if (event.isUpdate) {
83+
// Wires up event that is listened to in the use-service-worker composable. The app-manual-update component then
84+
// prompts the user there is an update available to activate.
85+
manualUpdateAvailable(registration)
86+
}
87+
})
88+
89+
wb.addEventListener('controlling', (event) => {
90+
console.log('sw: controlling event listener hit.')
91+
})
92+
93+
wb.addEventListener('fetch', (event) => {
94+
console.log('sw: fetch event listener hit.')
95+
// wb.messageSkipWaiting() // hack to "unfreeze" stuck service worker
96+
})
97+
}
98+
}
99+
100+
export default register

0 commit comments

Comments
 (0)