ReactJs PWA无法在iOS上更新

Spe*_*les 3 ios reactjs progressive-web-apps

我正在构建一个ReactJs PWA,但在检测iOS上的更新时遇到了麻烦。

在Android上,一切工作正常,因此我想知道这是否与iOS对PWA的支持有关,或者我对Service Worker的实现不好。

到目前为止,这是我所做的:

构建过程和托管

我的应用程序是使用webpack构建的,并托管在AWS上。大多数文件(js / css)的名称均由其内容生成的哈希值构成。对于那些不是(应用清单,index.html,sw.js)的应用程序,我确保AWS为它们提供了一些Cache-Control标头,以防止任何缓存。一切都通过https提供。

服务人员

我尽可能简化了这一过程:除了为我的应用程序外壳程序添加precache之外,我没有添加任何缓存规则:

workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
Run Code Online (Sandbox Code Playgroud)

服务人员注册

服务工作者的注册发生在主要的ReactJs App组件的componentDidMount()生命周期挂钩中:

componentDidMount() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
      .then((reg) => {
        reg.onupdatefound = () => {
          this.newWorker = reg.installing;
          this.newWorker.onstatechange = () => {
            if (this.newWorker.state === 'installed') {
              if (reg.active) {
                // a version of the SW is already up and running

                /*
                  code omitted: displays a snackbar to the user to manually trigger
                  activation of the new SW. This will be done by calling skipWaiting()
                  then reloading the page
                */
              } else {
                // first service worker registration, do nothing
              }
            }
          };
        };
      });
  }
}
Run Code Online (Sandbox Code Playgroud)

服务人员生命周期管理

根据Google关于服务人员的文档,导航到范围内的页面时应检测到服务人员的新版本。但是作为单页应用程序,加载该应用程序后不会发生任何困难的导航。

我为此找到的解决方法是挂接到react-router并侦听路由更改,然后手动要求注册的服务工作者进行自我更新:

const history = createBrowserHistory(); // from 'history' node package
history.listen(() => {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker
      .getRegistration()
      .then((reg) => {

        if (!reg) {
          return null;
        }

        reg.update();
      });
  }
});

Run Code Online (Sandbox Code Playgroud)

实际行为

alert()在上面显示的代码中到处都是一堆,这就是我观察到的:

  1. 将pwa添加到主屏幕后首次打开pwa时,将按预期在Android和iOS上注册服务工作者
  2. 在保持应用程序打开的同时,我在AWS上部署了一个新版本。借助我的历史记录侦听器,在应用程序中导航会触发手动更新。找到新版本,安装在后台。然后显示我的快餐栏,我可以触发切换到新软件。
  3. 现在,我关闭该应用程序并在AWS上部署新版本。再次打开应用程序时:
    • 在Android上,更新会在Android重新加载页面后立即发现
    • iOS并非如此,因此我需要在应用程序中导航以便历史记录侦听器触发搜索更新。这样做时,找到更新
    • 之后,对于两个操作系统,都会显示我的快餐栏,并且可以触发切换到新的软件
  4. 现在,我关闭应用程序并关闭手机。部署新版本后,我再次启动它们并打开应用程序:
    • 在Android上,就像以前一样,将重新加载页面以检测更新,然后显示小吃栏等。
    • 在iOS上,我在应用程序中导航,并且侦听器触发搜索更新。但是这次,从未找到新版本,onupdatefound也从未触发过我的事件处理程序

从Maximiliano Firtman的Medium上阅读这篇文章,似乎iOS 12.2为PWA带来了新的生命周期。据他介绍,当应用程序长时间闲置或在设备重新引导期间,应用程序状态以及页面都将被杀死。

我想知道这是否可能是造成我的问题的根本原因,但是到目前为止,我找不到任何遇到同样麻烦的人。

Spe*_*les 5

So after a lot of digging and investigation, I finally found out what was my problem.

From what I was able to observe, I think there is a little difference in the way Android and iOS handle PWAs lifecycle, as well as service workers.

On Android, when starting the app after a reboot, it looks like starting the app and searching an update of the service worker (thanks to the hard navigation occuring when reloading the page) are 2 tasks done in parallel. By doing that, the app have enough time to subscribe to the already existing service worker and define a onupdatefound() handler before the new version of the service worker is found.

On the other hand with iOS, it seems that when you start the app after a reboot of the device (or after not using it for a long period, see Medium article linked in the main topic), iOS triggers the search for an update before starting your app. And if an update is found, it will be installed and and enter its 'waiting' status before the app is actually started. This is probably what happens when the splashscreen is displayed... So in the end, when your app finally starts and you subscribe to the already existing service worker to define your onupdatefound() handler, the update has already been installed and is waiting to take control of the clients.

So here is my final code to register the service worker :

componentDidMount() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
      .then((reg) => {

        if (reg.waiting) {
          // a new version is already waiting to take control
          this.newWorker = reg.waiting;

          /*
            code omitted: displays a snackbar to the user to manually trigger
            activation of the new SW. This will be done by calling skipWaiting()
            then reloading the page
          */
        }

        // handler for updates occuring while the app is running, either actively or in the background
        reg.onupdatefound = () => {
          this.newWorker = reg.installing;

          this.newWorker.onstatechange = () => {
            if (this.newWorker.state === 'installed') {
              if (reg.active) {
                // a version of the SW already has control over the app

                /*
                  same code omitted
                */
              } else {
                // very first service worker registration, do nothing
              }
            }
          };
        };
      });
  }
}
Run Code Online (Sandbox Code Playgroud)

Note :

I also got rid of my listener on history that I used to trigger the search for an update on every route change, as it seemed overkill. Now I rely on the Page Visibility API to trigger this search every time the app gets the focus :

// this function is called in the service worker registration promise, providing the ServiceWorkerRegistration instance
const registerPwaOpeningHandler = (reg) => {
    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
            reg.update();
        }
    });

    return reg;
};
Run Code Online (Sandbox Code Playgroud)

  • 你能修改create React App Service Worker并在github上制作一个示例吗 (3认同)