ReactJs PWA не обновляется на iOS - PullRequest
1 голос
/ 09 апреля 2019

Я создаю ReactJs PWA, но у меня возникают проблемы с обнаружением обновлений на iOS.

На Android все работает отлично, поэтому мне интересно, связано ли все это с поддержкой iOS для PWA илиесли моя реализация работника службы не подходит.

Вот что я сделал до сих пор:

Процесс сборки и хостинг

Мое приложениепостроен с использованием веб-пакета и размещен на AWS.Большинство файлов (js / css) построены с хэшем в имени, сгенерированным из их содержимого.Для тех, кто не (app манифест, index.html, sw.js), я убедился, что AWS предоставляет им некоторые заголовки Cache-Control, предотвращающие любой кеш.Все обслуживается через https.

Service Worker

Я сохранил это как можно проще: я не добавил никаких правил кэширования, кроме предварительного кэширования для моей оболочки приложения:

workbox.precaching.precacheAndRoute(self.__precacheManifest || []);

Регистрация работника службы

Регистрация работника службы происходит в основном компоненте приложения ReactJs, в хуке жизненного цикла 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
              }
            }
          };
        };
      });
  }
}

Управление жизненным циклом работника службы

Согласно документации Google о работниках службы , при переходе к службе должна быть обнаружена новая версия работника службы.страница в области видимости.Но как одностраничное приложение, после его загрузки не происходит сложной навигации.

Обходной путь, который я нашел для этого, заключается в том, чтобы подключиться к реагирующему маршрутизатору и прослушать изменения маршрута, а затем вручную запроситьзарегистрированный работник сервиса для обновления:

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

        if (!reg) {
          return null;
        }

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

Фактическое поведение

Бросив кучу alert() повсюду в коде, показанном выше, это то, что я наблюдаю:

  1. При первом открытии pwa после добавления его на домашний экран сервисный работник регистрируется должным образом на Android и iOS.
  2. . Оставляя приложение открытым, я устанавливаю новую версию на AWS.Навигация в приложении вызывает обновление вручную благодаря моему слушателю истории.Новая версия найдена, установлена ​​в фоновом режиме.Затем отображается моя закусочная, и я могу переключиться на новое ПО.
  3. Теперь я закрываю приложение и развертываю новую версию в AWS.При повторном открытии приложения:
    • На Android обновление обнаруживается сразу же, поскольку Android перезагружает страницу
    • iOS не выполняет, поэтому мне нужно перейти в приложение, чтобы мой прослушиватель истории запустил поискдля обновления.При этом обнаруживается обновление
    • После этого для обеих ОС отображается моя снэк-бар и я могу переключиться на новый SW
  4. Теперь я закрываюприложение и отключают телефоны.После развертывания новой версии я снова запускаю их и открываю приложение:
    • На Android, как и раньше, перезагружается страница, которая обнаруживает обновление, затем отображается снэк-бар и т. Д.
    • В iOS я перемещаюсь по приложению, и мой слушатель запускает поиск обновлений. Но на этот раз новая версия не найдена, и мой обработчик событий onupdatefound никогда не сработает

Чтение этой записина Medium от Maximiliano Firtman кажется, что iOS 12.2 принесла новый жизненный цикл для PWA.По его словам, когда приложение долго простаивает или во время перезагрузки устройства, состояние приложения уничтожается, как и страница.

Мне интересно, не может ли это быть основной причиной моей проблемы здесь, но я не смог найти никого, кто до сих пор сталкивался с такой же проблемой.

1 Ответ

3 голосов
/ 10 апреля 2019

Итак, после долгих поисков и исследований, я наконец-то выяснил, в чём была моя проблема.

Из того, что я смог наблюдать, я думаю, что есть небольшая разница в том, как Android и iOS обрабатывают жизненный цикл PWA, а также сервисные работники.

В Android при запуске приложения после перезагрузки это выглядит как запуск приложения и поиск обновлений сервисного работника (благодаря сложной навигации, возникающей при перезагрузке страницы), это две задачи, выполняемые параллельно. Таким образом, у приложения будет достаточно времени, чтобы подписаться на уже существующего работника сервиса и определить обработчик onupdatefound() до того, как будет найдена новая версия работника сервиса.

С другой стороны, с iOS, кажется, что когда вы запускаете приложение после перезагрузки устройства (или после того, как вы не используете его в течение длительного периода времени, см. Статью «Средняя» в основной теме), iOS запускает поиск обновление перед запуском приложения. И если обновление найдено, оно будет установлено и войдет в статус «ожидания» до , когда приложение действительно будет запущено. Это, вероятно, то, что происходит, когда отображается заставка ... Таким образом, в конце концов, когда ваше приложение, наконец, запускается, и вы подписываетесь на уже существующего сервисного работника, чтобы определить свой обработчик onupdatefound(), обновление уже установлено и ждет, чтобы получить контроль над клиентами.

Итак, вот мой окончательный код для регистрации работника сервиса:

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
              }
            }
          };
        };
      });
  }
}

Примечание:

Я также избавился от прослушивателя истории, который использовал для поиска обновлений при каждом изменении маршрута, поскольку это казалось излишним. Теперь я полагаюсь на API видимости страницы, чтобы запускать этот поиск каждый раз, когда приложение получает фокус:

// 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;
};
...