更新PWA React App(使用CRA)使用用户单击按钮通过服务工作者

发布于 2025-01-30 18:18:35 字数 1834 浏览 3 评论 0原文

我想在可用新内容时更新PWA应用程序。既然我们无法从服务工作者访问DOM,我该怎么做? 服务工作者正确识别新内容,但是我无法绑定钩子或上下文以显示React中的模式或更新按钮。有帮助吗?

服务工作者寄存器 onupdate 函数:

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) {
          // At this point, the updated precached content has been fetched,
          // but the previous service worker will still serve the older
          // content until all client tabs are closed.
          // setUpdate(true);
          // registration.waiting.postMessage({ type: 'SKIP_WAITING' });
          console.log(
            'New content is available and will be used when all ' +
            'tabs for this page are closed. See https://cra.link/PWA.'
          );

          // Execute callback
          if (config && config.onUpdate) {
            config.onUpdate(registration);
          }
        } 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.');

          // Execute callback
          if (config && config.onSuccess) {
            config.onSuccess(registration);
          }
        }
      }
    };
  };
})
.catch((error) => {
  console.error('Error during service worker registration:', error);
});
}

此功能在 serviceworkerRegistration.js 文件中。 我们可以添加注册。我该如何解决?

I want to update PWA application when new content is available. Since we can't access DOM from service worker, how can I exactly do that?
The service worker correctly recognize the new content, but I can't bind a hook or context to show my modal or update button in react. Any help?

service worker register and onUpdate function:

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) {
          // At this point, the updated precached content has been fetched,
          // but the previous service worker will still serve the older
          // content until all client tabs are closed.
          // setUpdate(true);
          // registration.waiting.postMessage({ type: 'SKIP_WAITING' });
          console.log(
            'New content is available and will be used when all ' +
            'tabs for this page are closed. See https://cra.link/PWA.'
          );

          // Execute callback
          if (config && config.onUpdate) {
            config.onUpdate(registration);
          }
        } 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.');

          // Execute callback
          if (config && config.onSuccess) {
            config.onSuccess(registration);
          }
        }
      }
    };
  };
})
.catch((error) => {
  console.error('Error during service worker registration:', error);
});
}

this function is in the serviceWorkerRegistration.js file.
We can add registration.waiting.postMessage({ type: 'SKIP_WAITING' }); function to call skipWaiting but it doesn't wait for user interaction and can't reload the page. how can I solve that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

┾廆蒐ゝ 2025-02-06 18:18:35

注册服务工作者时,您可以尝试通过Update功能传递。在该功能中,遵循页面重新加载后,向服务工作者发送Skip_waiting消息。

 onUpdate: (registration) => {
    console.log("SW: sending SKIP_WAITING message to get updated serviceworker to take effect now (without manual restart)")
    // Old serviceworker will be stopped/new serviceworker started without reload
    registration.waiting.postMessage({type: 'SKIP_WAITING'})
    window.location.reload()
}

您的服务器工人应具有SKIP_WAITING消息厂商。这样的事情:

self.addEventListener('message', (event) => {
console.log("SW: received message event = "+JSON.stringify(event))
if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
}});

这对我有用。

我仍在努力的是,如果用户打开应用程序,则如何更新应用程序。与此一样,

  1. 用户浏览到PWA URL
  2. 用户添加到主屏幕
  3. 用户关闭浏览器选项卡
  4. 用户从主屏幕上打开PWA

永远打开它...

,并且只会 对您的用例。

You can try passing in an onUpdate function when you register the service worker. In that function send a SKIP_WAITING message to the service worker followed a page reload.

 onUpdate: (registration) => {
    console.log("SW: sending SKIP_WAITING message to get updated serviceworker to take effect now (without manual restart)")
    // Old serviceworker will be stopped/new serviceworker started without reload
    registration.waiting.postMessage({type: 'SKIP_WAITING'})
    window.location.reload()
}

Your server worker should have SKIP_WAITING message hander. Something like this:

self.addEventListener('message', (event) => {
console.log("SW: received message event = "+JSON.stringify(event))
if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
}});

This is working for me.

What I am still struggling with is how to get the app updated if the user leaves the app open. As in,

  1. User browses to PWA url
  2. User adds to Home Screen
  3. User closes browser tab
  4. User opens PWA from Home Screen

and just leaves it open forever and ever...

This should probably go in a new question, but I thought it might be relevant to your use case.

决绝 2025-02-06 18:18:35

基本上,例如,您有一个PWA组件。
在此组件中,您可以创建一个对话模式,以显示用户新更新。
然后,您在componentDidmountuseffect(()=> {},[],[])中注册此组件内部的PWA。
但是,这样,您将更新功能传递到这样的登记册中。

// ============== serviceworkerRegisteration.js

/* eslint-disable no-param-reassign */
// This optional code is used to register a service worker.
// register() is not called by default.

// 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 subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.

// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA

const isLocalhost = Boolean(
    window.location.hostname === 'localhost' ||
        // [::1] is the IPv6 localhost address.
        window.location.hostname === '[::1]' ||
        // 127.0.0.0/8 are considered localhost for IPv4.
        window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);

// this part of code added for the times user close pwa or minimize it then comeback
// and we want to check for new updates
const registerPwaOpeningHandler = (registration, callback) => {
    let hidden;
    let visibilityChange;
    if (typeof document.hidden !== 'undefined') {
        // Opera 12.10 and Firefox 18 and later support
        hidden = 'hidden';
        visibilityChange = 'visibilitychange';
    } else if (typeof document.msHidden !== 'undefined') {
        hidden = 'msHidden';
        visibilityChange = 'msvisibilitychange';
    } else if (typeof document.webkitHidden !== 'undefined') {
        hidden = 'webkitHidden';
        visibilityChange = 'webkitvisibilitychange';
    }

    window.document.addEventListener(visibilityChange, () => {
        if (!document[hidden]) {
            // manually force detection of a potential update when the pwa is opened
            registration.update();
            if (callback) callback();
        }
    });
};

function registerValidSW(swUrl, config) {
    navigator.serviceWorker
        .register(swUrl)
        .then((registration) => {
            registerPwaOpeningHandler(registration, () => {
                const waitingWorker = registration.waiting;
                if (waitingWorker && waitingWorker.state === 'installed') {
                    if (config && config.onUpdate) {
                        config.onUpdate(registration);
                    }
                }
            });

            const waitingWorker = registration.waiting;
            if (waitingWorker && waitingWorker.state === 'installed') {
                if (config && config.onUpdate) {
                    config.onUpdate(registration);
                }
            }

            registration.onupdatefound = () => {
                const installingWorker = registration.installing;
                if (installingWorker == null) {
                    return;
                }

                installingWorker.onstatechange = () => {
                    if (installingWorker.state === 'installed') {
                        if (navigator.serviceWorker.controller) {
                            // At this point, the updated precached content has been fetched,
                            // but the previous service worker will still serve the older
                            // content until all client tabs are closed.
                            // eslint-disable-next-line no-console
                            console.log(
                                'New content is available and will be used when all ' +
                                    'tabs for this page are closed. See https://cra.link/PWA.'
                            );

                            // Execute callback
                            if (config && config.onUpdate) {
                                config.onUpdate(registration);
                            }
                        } else {
                            // At this point, everything has been precached.
                            // It's the perfect time to display a
                            // "Content is cached for offline use." message.
                            // eslint-disable-next-line no-console
                            console.log('Content is cached for offline use.');

                            // Execute callback
                            if (config && config.onSuccess) {
                                config.onSuccess(registration);
                            }
                        }
                    }
                };
            };
        })
        .catch((error) => {
            // eslint-disable-next-line no-console
            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, {
        headers: { 'Service-Worker': 'script' }
    })
        .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(() => {
            // eslint-disable-next-line no-console
            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) {
        // The URL constructor is available in all browsers that support SW.
        const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
        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/facebook/create-react-app/issues/2374
            return;
        }

        const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;

        if (isLocalhost) {
            // This is running on localhost. Let's check if a service worker still exists or not.
            checkValidServiceWorker(swUrl, config);
            // Add some additional logging to localhost, pointing developers to the
            // service worker/PWA documentation.
            navigator.serviceWorker.ready.then(() => {
                // eslint-disable-next-line no-console
                console.log(
                    'This web app is being served cache-first by a service ' +
                        'worker. To learn more, visit https://cra.link/PWA'
                );
            });
        } 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();
            })
            .catch((error) => {
                // eslint-disable-next-line no-console
                console.error(error.message);
            });
    }
}

// ====================== pwa/index.js

import { useEffect, useState } from 'react';
import * as serviceWorker from '../serviceWorkerRegistration';
import UpdateVersionModal from './modal';

export default function Pwa() {
    const [registerWating, setRegisterWating] = useState(null);

    useEffect(() => {
        serviceWorker.register({
            onUpdate: (registration) => {
                if (registration && registration.waiting) {
                    setRegisterWating(registration);
                }
            }
        });
    }, []);

    const handleUpdate = () => {
        registerWating?.postMessage({ type: 'SKIP_WAITING' });
        setRegisterWating(null);
    };

    return (
        <UpdateVersionModal
            name="update-modal-version"
            open={Boolean(registerWating)}
            onClose={() => setRegisterWating(false)}
            onUpdate={handleUpdate}
        />
    );
}

// ================== pwa/dialog.js

/* eslint-disable react/prop-types */
import React from 'react';
import { Button } from '@mui/material';
import { ModalDialog, ModalContent, ModalAction } from './style';

class UpdateModal extends React.PureComponent {
    render() {
        const { open, onClose, onUpdate } = this.props;
        return (
            <ModalDialog open={open}>
                {/* <ModalTitle>asd</ModalTitle> */}
                <ModalContent>
                    <img alt="new update" src="/images/new-update.svg" className="update-image" />
                    <div className="content-text">
                        <p className="title">We’er Better Than Ever</p>
                        <p className="discription">
                            This version is no longer supported. Please update to the latest
                            version. Thanks
                        </p>
                    </div>
                </ModalContent>
                <ModalAction>
                    <Button color="primary" variant="contained" onClick={onClose}>
                        cancel
                    </Button>
                    <Button color="primary" variant="outlined" onClick={onUpdate}>
                        Update
                    </Button>
                </ModalAction>
            </ModalDialog>
        );
    }
}

export default UpdateModal;

registerpwaopeninghandler添加到service> serviceworkerRegisteration for for for for for Of For兼容性,例如用户取消更新模式并在他再次卷回我们的选项卡后切换到另一个选项卡时,我们再次显示对话框。

basically, you have a PWA component for example.
in this component, you create a dialog modal to show the user new updates.
then you register the PWA inside of this component in componentDidMount or useEffect(() => {} , []).
but in this way you pass an update function into register like this.

// ============= ServiceWorkerRegisteration.js

/* eslint-disable no-param-reassign */
// This optional code is used to register a service worker.
// register() is not called by default.

// 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 subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.

// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA

const isLocalhost = Boolean(
    window.location.hostname === 'localhost' ||
        // [::1] is the IPv6 localhost address.
        window.location.hostname === '[::1]' ||
        // 127.0.0.0/8 are considered localhost for IPv4.
        window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);

// this part of code added for the times user close pwa or minimize it then comeback
// and we want to check for new updates
const registerPwaOpeningHandler = (registration, callback) => {
    let hidden;
    let visibilityChange;
    if (typeof document.hidden !== 'undefined') {
        // Opera 12.10 and Firefox 18 and later support
        hidden = 'hidden';
        visibilityChange = 'visibilitychange';
    } else if (typeof document.msHidden !== 'undefined') {
        hidden = 'msHidden';
        visibilityChange = 'msvisibilitychange';
    } else if (typeof document.webkitHidden !== 'undefined') {
        hidden = 'webkitHidden';
        visibilityChange = 'webkitvisibilitychange';
    }

    window.document.addEventListener(visibilityChange, () => {
        if (!document[hidden]) {
            // manually force detection of a potential update when the pwa is opened
            registration.update();
            if (callback) callback();
        }
    });
};

function registerValidSW(swUrl, config) {
    navigator.serviceWorker
        .register(swUrl)
        .then((registration) => {
            registerPwaOpeningHandler(registration, () => {
                const waitingWorker = registration.waiting;
                if (waitingWorker && waitingWorker.state === 'installed') {
                    if (config && config.onUpdate) {
                        config.onUpdate(registration);
                    }
                }
            });

            const waitingWorker = registration.waiting;
            if (waitingWorker && waitingWorker.state === 'installed') {
                if (config && config.onUpdate) {
                    config.onUpdate(registration);
                }
            }

            registration.onupdatefound = () => {
                const installingWorker = registration.installing;
                if (installingWorker == null) {
                    return;
                }

                installingWorker.onstatechange = () => {
                    if (installingWorker.state === 'installed') {
                        if (navigator.serviceWorker.controller) {
                            // At this point, the updated precached content has been fetched,
                            // but the previous service worker will still serve the older
                            // content until all client tabs are closed.
                            // eslint-disable-next-line no-console
                            console.log(
                                'New content is available and will be used when all ' +
                                    'tabs for this page are closed. See https://cra.link/PWA.'
                            );

                            // Execute callback
                            if (config && config.onUpdate) {
                                config.onUpdate(registration);
                            }
                        } else {
                            // At this point, everything has been precached.
                            // It's the perfect time to display a
                            // "Content is cached for offline use." message.
                            // eslint-disable-next-line no-console
                            console.log('Content is cached for offline use.');

                            // Execute callback
                            if (config && config.onSuccess) {
                                config.onSuccess(registration);
                            }
                        }
                    }
                };
            };
        })
        .catch((error) => {
            // eslint-disable-next-line no-console
            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, {
        headers: { 'Service-Worker': 'script' }
    })
        .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(() => {
            // eslint-disable-next-line no-console
            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) {
        // The URL constructor is available in all browsers that support SW.
        const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
        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/facebook/create-react-app/issues/2374
            return;
        }

        const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;

        if (isLocalhost) {
            // This is running on localhost. Let's check if a service worker still exists or not.
            checkValidServiceWorker(swUrl, config);
            // Add some additional logging to localhost, pointing developers to the
            // service worker/PWA documentation.
            navigator.serviceWorker.ready.then(() => {
                // eslint-disable-next-line no-console
                console.log(
                    'This web app is being served cache-first by a service ' +
                        'worker. To learn more, visit https://cra.link/PWA'
                );
            });
        } 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();
            })
            .catch((error) => {
                // eslint-disable-next-line no-console
                console.error(error.message);
            });
    }
}

// ============= PWA/index.js

import { useEffect, useState } from 'react';
import * as serviceWorker from '../serviceWorkerRegistration';
import UpdateVersionModal from './modal';

export default function Pwa() {
    const [registerWating, setRegisterWating] = useState(null);

    useEffect(() => {
        serviceWorker.register({
            onUpdate: (registration) => {
                if (registration && registration.waiting) {
                    setRegisterWating(registration);
                }
            }
        });
    }, []);

    const handleUpdate = () => {
        registerWating?.postMessage({ type: 'SKIP_WAITING' });
        setRegisterWating(null);
    };

    return (
        <UpdateVersionModal
            name="update-modal-version"
            open={Boolean(registerWating)}
            onClose={() => setRegisterWating(false)}
            onUpdate={handleUpdate}
        />
    );
}

// ============= PWA/dialog.js

/* eslint-disable react/prop-types */
import React from 'react';
import { Button } from '@mui/material';
import { ModalDialog, ModalContent, ModalAction } from './style';

class UpdateModal extends React.PureComponent {
    render() {
        const { open, onClose, onUpdate } = this.props;
        return (
            <ModalDialog open={open}>
                {/* <ModalTitle>asd</ModalTitle> */}
                <ModalContent>
                    <img alt="new update" src="/images/new-update.svg" className="update-image" />
                    <div className="content-text">
                        <p className="title">We’er Better Than Ever</p>
                        <p className="discription">
                            This version is no longer supported. Please update to the latest
                            version. Thanks
                        </p>
                    </div>
                </ModalContent>
                <ModalAction>
                    <Button color="primary" variant="contained" onClick={onClose}>
                        cancel
                    </Button>
                    <Button color="primary" variant="outlined" onClick={onUpdate}>
                        Update
                    </Button>
                </ModalAction>
            </ModalDialog>
        );
    }
}

export default UpdateModal;

registerPwaOpeningHandler function added to ServiceWorkerRegisteration for more compatibility like when user cancel the update modal and switch to another tab after he comeback to our tab again we show the dialog once more.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文