Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client): multiple clients with prompt for update #394

Merged
merged 5 commits into from Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 31 additions & 0 deletions client.d.ts
Expand Up @@ -20,6 +20,12 @@ declare module 'virtual:pwa-register' {
onRegisterError?: (error: any) => void
}

/**
* Registers the service worker returning a callback to reload the current page when an update is found.
*
* @param options the options to register the service worker.
* @return (reloadPage?: boolean) => Promise<void> From version 0.13.2+ `reloadPage` param is not used anymore.
*/
export function registerSW(options?: RegisterSWOptions): (reloadPage?: boolean) => Promise<void>
}

Expand Down Expand Up @@ -52,6 +58,11 @@ declare module 'virtual:pwa-register/vue' {
export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: Ref<boolean>
offlineReady: Ref<boolean>
/**
* Reloads the current window to allow the service worker take the control.
*
* @param reloadPage From version 0.13.2+ this param is not used anymore.
*/
updateServiceWorker: (reloadPage?: boolean) => Promise<void>
}
}
Expand Down Expand Up @@ -85,6 +96,11 @@ declare module 'virtual:pwa-register/svelte' {
export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: Writable<boolean>
offlineReady: Writable<boolean>
/**
* Reloads the current window to allow the service worker take the control.
*
* @param reloadPage From version 0.13.2+ this param is not used anymore.
*/
updateServiceWorker: (reloadPage?: boolean) => Promise<void>
}
}
Expand Down Expand Up @@ -118,6 +134,11 @@ declare module 'virtual:pwa-register/react' {
export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: [boolean, Dispatch<SetStateAction<boolean>>]
offlineReady: [boolean, Dispatch<SetStateAction<boolean>>]
/**
* Reloads the current window to allow the service worker take the control.
*
* @param reloadPage From version 0.13.2+ this param is not used anymore.
*/
updateServiceWorker: (reloadPage?: boolean) => Promise<void>
}
}
Expand Down Expand Up @@ -151,6 +172,11 @@ declare module 'virtual:pwa-register/solid' {
export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: [Accessor<boolean>, Setter<boolean>]
offlineReady: [Accessor<boolean>, Setter<boolean>]
/**
* Reloads the current window to allow the service worker take the control.
*
* @param reloadPage From version 0.13.2+ this param is not used anymore.
*/
updateServiceWorker: (reloadPage?: boolean) => Promise<void>
}
}
Expand Down Expand Up @@ -184,6 +210,11 @@ declare module 'virtual:pwa-register/preact' {
export function useRegisterSW(options?: RegisterSWOptions): {
needRefresh: [boolean, StateUpdater<boolean>]
offlineReady: [boolean, StateUpdater<boolean>]
/**
* Reloads the current window to allow the service worker take the control.
*
* @param reloadPage From version 0.13.2+ this param is not used anymore.
*/
updateServiceWorker: (reloadPage?: boolean) => Promise<void>
}
}
1 change: 0 additions & 1 deletion docs/.vitepress/components.d.ts
Expand Up @@ -13,7 +13,6 @@ declare module '@vue/runtime-core' {
ExamplesInjectManifest: typeof import('./theme/components/ExamplesInjectManifest.md')['default']
GenerateSWCleanupOutdatedCaches: typeof import('./theme/components/GenerateSWCleanupOutdatedCaches.md')['default']
GenerateSWSourceMap: typeof import('./theme/components/GenerateSWSourceMap.md')['default']
HeuristicWorkboxWindow: typeof import('./theme/components/HeuristicWorkboxWindow.md')['default']
HomePage: typeof import('./theme/components/HomePage.vue')['default']
InjectManifestCleanupOutdatedCaches: typeof import('./theme/components/InjectManifestCleanupOutdatedCaches.md')['default']
InjectManifestSourceMap: typeof import('./theme/components/InjectManifestSourceMap.md')['default']
Expand Down
5 changes: 0 additions & 5 deletions docs/.vitepress/theme/components/HeuristicWorkboxWindow.md

This file was deleted.

3 changes: 0 additions & 3 deletions docs/examples/preact.md
Expand Up @@ -31,9 +31,6 @@ Done. Now run:

To test `new content available`, you should rerun the corresponding script, and then refresh the page.

If you are running an example with `Periodic SW updates`, you will need to wait 1 minute:
<HeuristicWorkboxWindow />

## Executing the examples

<RunExamples />
Expand Down
3 changes: 0 additions & 3 deletions docs/examples/react.md
Expand Up @@ -31,9 +31,6 @@ Done. Now run:

To test `new content available`, you should rerun the corresponding script, and then refresh the page.

If you are running an example with `Periodic SW updates`, you will need to wait 1 minute:
<HeuristicWorkboxWindow />

## Executing the examples

<RunExamples />
Expand Down
3 changes: 0 additions & 3 deletions docs/examples/solidjs.md
Expand Up @@ -20,9 +20,6 @@ npx degit solidjs/templates/ts-router solid-router

To test `new content available`, you should rerun the corresponding script, and then refresh the page.

If you are running an example with `Periodic SW updates`, you will need to wait 1 minute:
<HeuristicWorkboxWindow />

## Executing the examples

<RunExamples />
Expand Down
3 changes: 0 additions & 3 deletions docs/examples/svelte.md
Expand Up @@ -30,9 +30,6 @@ Done. Now run:

To test `new content available`, you should rerun the corresponding script, and then refresh the page.

If you are running an example with `Periodic SW updates`, you will need to wait 1 minute:
<HeuristicWorkboxWindow />

## Executing the examples

<RunExamples />
Expand Down
4 changes: 0 additions & 4 deletions docs/examples/sveltekit.md
Expand Up @@ -47,10 +47,6 @@ Next steps:

To test `new content available`, you should rerun the corresponding script, and then refresh the page.

If you are running an example with `Periodic SW updates`, you will need to wait 1 minute:

<HeuristicWorkboxWindow />

## Executing the examples

<RunExamples />
Expand Down
3 changes: 0 additions & 3 deletions docs/examples/vue.md
Expand Up @@ -31,9 +31,6 @@ Done. Now run:

To test `new content available`, you should rerun the corresponding script, and then refresh the page.

If you are running an example with `Periodic SW updates`, you will need to wait 1 minute:
<HeuristicWorkboxWindow />

## Executing the examples

<RunExamples />
Expand Down
2 changes: 0 additions & 2 deletions docs/frameworks/preact.md
Expand Up @@ -157,5 +157,3 @@ const updateServiceWorker = useRegisterSW({
```

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

<HeuristicWorkboxWindow />
2 changes: 0 additions & 2 deletions docs/frameworks/react.md
Expand Up @@ -158,5 +158,3 @@ const updateServiceWorker = useRegisterSW({
```

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

<HeuristicWorkboxWindow />
2 changes: 0 additions & 2 deletions docs/frameworks/solidjs.md
Expand Up @@ -162,5 +162,3 @@ const updateServiceWorker = useRegisterSW({
```

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

<HeuristicWorkboxWindow />
3 changes: 0 additions & 3 deletions docs/frameworks/svelte.md
Expand Up @@ -150,6 +150,3 @@ const updateServiceWorker = useRegisterSW({
```

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

<HeuristicWorkboxWindow />

4 changes: 0 additions & 4 deletions docs/frameworks/vue.md
Expand Up @@ -140,8 +140,6 @@ const updateServiceWorker = useRegisterSW({

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

<HeuristicWorkboxWindow />

## Vue 2

Since this plugin only supports `Vue 3`, you cannot use the virtual module `virtual:pwa-register/vue`.
Expand Down Expand Up @@ -297,5 +295,3 @@ export default {
```

The interval must be in milliseconds, in the example above it is configured to check the service worker every hour.

<HeuristicWorkboxWindow />
4 changes: 0 additions & 4 deletions docs/guide/development.md
Expand Up @@ -191,10 +191,6 @@ if ('serviceWorker' in navigator) {
}
```

When you change your service worker source code, `Vite` will force a full reload, since we're using `workbox-window` to register it (by default, you can register it manually) you may have some problems with the service worker events.

<HeuristicWorkboxWindow />

## Example

You can find an example here: [vue-router](https://github.com/antfu/vite-plugin-pwa/tree/main/examples/vue-router).
Expand Down
9 changes: 5 additions & 4 deletions docs/guide/periodic-sw-updates.md
Expand Up @@ -56,8 +56,11 @@ const updateSW = registerSW({
return

const resp = await fetch(swUrl, {
'cache': 'no-store',
'cache-control': 'no-cache'
cache: 'no-store',
headers: {
'cache': 'no-store',
'cache-control': 'no-cache',
},
})

if (resp?.status === 200)
Expand All @@ -67,5 +70,3 @@ const updateSW = registerSW({
})
```
:::

<HeuristicWorkboxWindow />
37 changes: 20 additions & 17 deletions src/client/build/register.ts
Expand Up @@ -29,43 +29,38 @@ export function registerSW(options: RegisterSWOptions = {}) {
let registerPromise: Promise<void>
let sendSkipWaitingMessage: () => Promise<void> | undefined

const updateServiceWorker = async (reloadPage = true) => {
const updateServiceWorker = async (_reloadPage = true) => {
await registerPromise
if (!auto) {
// Assuming the user accepted the update, set up a listener
// that will reload the page as soon as the previously waiting
// service worker has taken control.
if (reloadPage) {
wb?.addEventListener('controlling', (event) => {
if (event.isUpdate)
window.location.reload()
})
}

await sendSkipWaitingMessage?.()
}
}

async function register() {
if ('serviceWorker' in navigator) {
const { Workbox, messageSW } = await import('workbox-window')
const { Workbox } = await import('workbox-window')
// __SW__, __SCOPE__ and __TYPE__ will be replaced by virtual module
wb = new Workbox('__SW__', { scope: '__SCOPE__', type: '__TYPE__' })
sendSkipWaitingMessage = async () => {
if (registration && registration.waiting) {
// Send a message to the waiting service worker,
// instructing it to activate.
// Note: for this to work, you have to add a message
// listener in your service worker. See below.
await messageSW(registration.waiting, { type: 'SKIP_WAITING' })
await wb?.messageSkipWaiting()
}
}
// __SW__, __SCOPE__ and __TYPE__ will be replaced by virtual module
wb = new Workbox('__SW__', { scope: '__SCOPE__', type: '__TYPE__' })

wb.addEventListener('activated', (event) => {
// this will only controls the offline request.
// This will only controls the offline request.
// event.isUpdate will be true if another version of the service
// worker was controlling the page when this version was registered.
if (event.isUpdate)
// When using multiple clients, if the client that fires the update is not the current one,
// workbox-window will fire this event with isUpdate=undefined and isExternal=true
// we only need to check this case and force reloading the page, otherwise use current logic
if (!event.isUpdate && event.isExternal)
window.location.reload()
else if (event.isUpdate)
auto && window.location.reload()
else if (!autoDestroy)
onOfflineReady?.()
Expand All @@ -81,6 +76,14 @@ export function registerSW(options: RegisterSWOptions = {}) {

// Assumes your app has some sort of prompt UI element
// that a user can either accept or reject.
// Assuming the user accepted the update, set up a listener
// that will reload the page as soon as the previously waiting
// service worker has taken control.
wb?.addEventListener('controlling', (event) => {
if (event.isUpdate)
window.location.reload()
})

onNeedRefresh?.()
}

Expand Down