Глубокое понимание PWA

внешний интерфейс браузер PWA

What is pwa?

Прогрессивное веб-приложение, или PWA для краткости, — это новый способ улучшить работу веб-приложения, предоставляя пользователям возможность работы с собственными приложениями.

PWA может получить опыт нативных приложений, не ссылаясь на конкретную технологию, а применяя некоторые новые технологии для ее улучшения, что значительно улучшило безопасность, производительность и опыт. PWA по сути является веб-приложением. Некоторые новые технологии также имеют некоторые функции собственного приложения и имеют преимущества как веб-приложения, так и собственного приложения.

Технологическая зависимость:

  • Service Worker
  • Веб-хранилище (IndexedDB, Кэши)
  • Fetch
  • Promises

PWA advantages

Приложение PWA должно быть:

  • обнаруживаемый, может быть идентифицирован как приложение, легко найти через поисковые системы
  • устанавливаемый устанавливаемый, доступен для мобильных устройств, добавляется на главный экран пользователя
  • Доступно для ссылок Доступно через URL-адрес, не требует сложной установки
  • независимый от сети может работать в автономном режиме или в низкоскоростной сетевой среде
  • Прогрессивное прогрессивное улучшение подходит для всех пользователей, поддерживаемые браузеры могут работать лучше, доступ к неподдерживаемым браузерам не будет затронут.
  • повторное вовлечение Повторное вовлечение Упростите взаимодействие пользователей с помощью напоминаний
  • отзывчивый для любого типа устройства
  • безопасный Механизм доставки контента предотвращает отслеживание и гарантирует, что контент не будет подделан

Progressive web app advantages. To find out how to implement PWAs, consult the guides listed in the below section.

прогрессивный

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

  • Первым шагом должна быть безопасность, и сделать весь сайт HTTPS, потому что это основа PWA, без HTTPS нет Service Worker
  • Вторым шагом должен быть Service Worker, чтобы улучшить базовую производительность, предоставить статические файлы в автономном режиме и улучшить работу пользователя с первого экрана.
  • Третий шаг, манифест приложения, этот шаг можно выполнить одновременно со вторым.
  • Позже рассмотрите другие функции, отправку сообщений в автономном режиме и т. д.

Уровень поддержки/покрытия

image

image

image

image

image

image

image

image

image

Service Worker

Service Worker, прокси-сервер между браузером и сетью, решает проблему кэширования ресурсов страницы и правильной работы в автономном режиме. Независимо от текущего процесса веб-страницы, он имеет собственный независимый рабочий контекст и не имеет доступа к модели DOM. В отличие от традиционных API, он неблокирует и возвращает результаты, когда они готовы, на основе методов обещаний. Это не только возможность работы в автономном режиме, но и такие функции, как уведомление о сообщениях и добавление значков на рабочий стол.

Предварительные условия

  • HTTPS, поскольку для Service Worker требуется среда HTTPS, мы можем изучить отладку с помощью GitHub Page. Общий браузер позволяет хосту Worker, когда Host является Localhost.
  • Механизм кэширования Service Worker зависит отCache APIосуществленный
  • полагатьсяHTML5 fetch API
  • полагатьсяPromiseвыполнить

Lifecycle

A more detailed introduction to The Service Worker Lifecycle

A service worker goes through three steps in its lifecycle:

  • Регистрация
  • Установка
  • Активация

1. Регистрация

Перед установкой Server Worker его необходимо зарегистрировать в JavaScript-коде основного процесса.Регистрация должна сообщить браузеру, какой файл Service Worker находится, после чего в фоновом режиме Service Worker начинает устанавливаться и активироваться.

Регистрационный код можно разместить в html файле<script></script>теги или по отдельностиmain.jsФайл импортируется в html-файл.

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
  .then(function(registration) {
    console.log('Registration successful, scope is:', registration.scope);
  })
  .catch(function(error) {
    console.log('Service worker registration failed, error:', error);
  });
}

В коде сначала проверьте, поддерживает ли браузер Service Worker, и если да, используйтеnavigator.serviceWorker.registerРегистрация, в случае успеха, будет в промисе.thenиди внутрьregistration.

Файл service-worker.js — это файл, в котором мы хотим написать функцию Service Worker.

При регистрации вы также можете указать необязательный параметр scope, который представляет собой область, к которой Service Worker может получить доступ, или каталог.

navigator.serviceWorker.register('/service-worker.js', {
  scope: '/app/'
});

Область действия, указанная в коде,/app/, что означает, что путь, которым может управлять Service Workder, подобенapp /app/home/ /app/abbout/и т. д. внутренние каталоги без возможности доступа/'/ изображения' и т. д./appЕще раз на пути слоя.

Если Service Worker уже установлен, повторная регистрация вернет текущий активный объект регистрации.

Браузер Chrome хорошо поддерживает функцию отладки Service Worker, которую можно ввести в браузере.chrome://inspect/#service-workersПроверьте, прошла ли регистрация успешно. Или просмотреть его в опции приложения консоли.

2. Установка

Событие установки привязано к файлу Service Worker.При успешной установке будет запущено событие установки. Как правило, мы будем обрабатывать кеш в событии установки, используя ранее упомянутыйCahce API, который является глобальным объектом на обслуживающем работнике[5], который может кэшировать соответствующие ресурсы сети и генерировать ключи в соответствии с их запросами. Этот API работает аналогично стандартному кешу браузера, но только для своей области. Кэш будет существовать всегда, пока он не будет очищен или обновлен вручную.

var cacheName = 'cachev1'
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(
        [
          '/css/bootstrap.css',
          '/css/main.css',
          '/js/bootstrap.min.js',
          '/js/jquery.min.js',
          '/offline.html'
        ]
      );
    })
  );
});
  1. Добавить прослушиватель для установки и использованияevent.waitUntil()чтобы сервисный работник неwaitUntil()Установка завершается до завершения выполнения.
  2. использоватьcaches.open()Создадим новый кеш cachev1 и вернем кешированный объект обещания.Когда он разрешен, мы используем его в методе thencaches.addAllЧтобы добавить список, который вы хотите кэшировать, список представляет собой массив, а URL-адрес внутри относится к источнику.
  3. Если обещание отклонено и установка не удалась, у нас нет подвоха, поэтому мы ничего не будем делать, мы также можем модифицировать код и добавить перерегистрированный код.
  4. После завершения установки Service Worker будет успешно активирован.

3. Активировать

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

Как обновляются сервис-воркеры?

service-worker.jsУправляет кешем ресурсов страницы и запросов.Если содержимое js обновляется, браузер получает новый файл при доступе к странице сайта, и если он сравнивает файл js побайтно и находит, что он отличается, он будет считать, что обновление запущено.алгоритм обновления, который устанавливает новый файл и запускает событие установки. Но старый сервис-воркер, который уже находится в активном состоянии, в это время все еще работает, а новый сервис-воркер перейдет в состояние ожидания после завершения установки. Пока все открытые страницы не будут закрыты, старый сервис-воркер автоматически останавливается, и новый сервис-воркер не вступит в силу на следующей повторно открытой странице.

Что делать, если вы хотите, чтобы все страницы своевременно обновлялись автоматически при выходе новой версии? Вы можете выполнить метод self.skipWaiting() в событии установки, чтобы пропустить состояние ожидания и перейти непосредственно к фазе активации. Затем, когда произойдет событие активации, обновите Service Worker на всех клиентах, выполнив метод self.clients.claim().

// 安装阶段跳过等待,直接进入 active
self.addEventListener('install', function (event) {
    event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([
            // 更新客户端
            self.clients.claim(),

            // 清理旧版本
            caches.keys().then(function (cacheList) {
                return Promise.all(
                    cacheList.map(function (cacheName) {
                        if (cacheName !== 'cachev1') {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});

Когда файл js может кэшироваться браузером, когда файл изменяется, браузер по-прежнему использует старый файл. Это может привести к неотвечающим обновлениям. Если вы столкнулись с этой проблемой, вы можете попробовать следующее: добавить правило фильтрации для файла на веб-сервере, не кэшировать или установить более короткий срок действия.

или вызовите его вручнуюupdate()обновить

navigator.serviceWorker.register('/service-worker.js').then(reg => {
  // sometime later…
  reg.update();
});

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

var version = 'v1';

navigator.serviceWorker.register('/service-worker.js').then(function (reg) {
    if (localStorage.getItem('sw_version') !== version) {
        reg.update().then(function () {
            localStorage.setItem('sw_version', version)
        });
    }
});

Схема

image

Каждое государство будет иметьing, в ходе выполнения.

image

Web Storage

Выбор правильного механизма хранения важен как для хранилища на локальном устройстве, так и для хранилища на облачном сервере. Хороший механизм хранения гарантирует надежное хранение информации, уменьшая пропускную способность и повышая скорость отклика. Правильная стратегия кэширования хранилища является основным строительным блоком для обеспечения автономной работы мобильного Интернета.

Тип хранилища, постоянство хранилища, поддержка браузера и другие причины, как хранить более эффективно, находятся в центре нашего обсуждения.

материал

Web Storage Overview

Using Cache API

Offine Storage for PWA

Best Practices for Using IndexedDB

Inspect and Manage Storage, Databases, and Caches

кеш API и IndexedDB

Рекомендации для автономного хранения данных могут включать:

  • Для адресуемых URL-адресов ресурсов, используйтеCache API(часть сервисного работника)
  • Для всех остальных данных используйте [IndexedDB}(developer.Mozilla.org/en-US/docs/…) (есть обертка обещания)

Фундаментальный

Оба вышеуказанных API являются асинхронными (IndexedDB основан на событиях, а Cache API — на Promise). они могут работать сweb Workers windows service workersиспользовать вместе. IndexedDB в основном можно использовать во всех браузерных средах (см. CanIUse выше).Поддержка Service Wokers и Cahce API показана на рисунке выше, поддерживаются Chrome, Firefox и Opera. Оболочка Promise IndexedDB скрывает некоторые из мощных, но в то же время очень сложных механизмов, поставляемых с библиотекой IndexedDB (например, транзакции, управление версиями схемы). IndexedDB будет поддерживать наблюдателей, функцию, позволяющую легко синхронизировать теги.

Для PWA мы можем кэшировать статические ресурсы, тем самым используя Application Shell (файлы JS/CSS/HTML), написанные с помощью Cache API, и заполнять данные офлайн-страницы из IndexedDB.

Для веб-хранилища (LocalStorage/SessionStorage) является синхронным, не поддерживает рабочие веб-потоки и имеет ограничения по размеру и типу (только строки).

добавить на рабочий стол

Разрешение добавлять сайты на главный экран — важная функция, предоставляемая PWA. Хотя некоторые браузеры в настоящее время поддерживают добавление ярлыков веб-страниц на главный экран, чтобы облегчить пользователям быстрый доступ к сайтам, PWA добавляет больше, чем просто ярлык веб-страницы на главный экран, он предоставляет больше функций и делает PWA более родным.

Функциональность, которую PWA добавляет на рабочий стол, зависит отmanifest.json.

Чтобы реализовать функцию добавления приложения PWA на рабочий стол, помимо требования, чтобы сайт поддерживал HTTPS, вам также необходимо подготовить файл manifest.json для настройки значка приложения, имени и другой информации. Например, базовый файл manifest.json должен содержать следующую информацию:

{
    "name": "Easyify Docs",
    "short_name": "Easyify Docs",
    "start_url": "/",
    "theme_color": "#FFDF00",
    "background_color": "#FFDF00",
    "display":"standalone",
    "description": "A compilation tools for FE, built with webpack4.x, compile faster and smart, make work easier.",
    "icons": [
      {
        "src": "./_assets/icons/32.png",
        "sizes": "32x32",
        "type": "image/png"
      }
    ],
    ...
  }

Разверните файл manifest.json в заголовке HTML-страницы сайта PWA, используя тег ссылки следующим образом:

<link rel="manifest" href="path-to-manifest/manifest.json">

Объяснение параметра:

name: {string} 应用名称,用于安装横幅、启动画面显示
short_name: {string} 应用短名称,用于主屏幕显示
icons: {Array.<ImageObject>} 应用图标列表
    src: {string} 图标 url
    type {string=} 图标的 mime 类型,非必填项,该字段可让浏览器快速忽略掉不支持的图标类型
    sizes {string} 图标尺寸,格式为widthxheight,宽高数值以 css 的 px 为单位。如果需要填写多个尺寸,则使用空格进行间隔,如"48x48 96x96 128x128"
start_url: {string=} 应用启动地址
scope: {string} 作用域
    // scope 应遵循如下规则:

    //如果没有在 manifest 中设置 scope,则默认的作用域为 manifest.json 所在文件夹;
    //scope 可以设置为 ../ 或者更高层级的路径来扩大PWA的作用域;
    //start_url 必须在作用域范围内;
    //如果 start_url 为相对地址,其根路径受 scope 所影响;
    //如果 start_url 为绝对地址(以 / 开头),则该地址将永远以 / 作为根地址;
background_color: {Color} css色值 可以指定启动画面的背景颜色。
display: {string} 显示类型
    //fullscreen	应用的显示界面将占满整个屏幕
    //standalone	浏览器相关UI(如导航栏、工具栏等)将会被隐藏
    //minimal-ui	显示形式与standalone类似,浏览器相关UI会最小化为一个按钮,不同浏览器在实现上略有不同
    //browser	浏览器模式,与普通网页在浏览器中打开的显示一致
orientation: string 应用显示方向
    //orientation属性的值有以下几种:
    //landscape-primary
    //landscape-secondary
    //landscape
    //portrait-primary
    //portrait-secondary
    //portrait
    //natural
    //any
theme_color: {Color} // css色值theme_color 属性可以指定 PWA 的主题颜色。可以通过该属性来控制浏览器 UI 的颜色。比如 PWA 启动画面上状态栏、内容页中状态栏、地址栏的颜色,会被 theme_color 所影响。
related_applications: Array.<AppInfo> 关联应用列表 可以引导用户下载原生应用
    platform: {string} 应用平台
    id: {string} 应用id

Push Notifications

Все уведомления — это сообщения, которые появляются на наших устройствах. Уведомления могут запускаться локально или отправляться сервером, а наше приложение в это время не запущено. Push-сообщение может создавать напоминания об обновлении приложения, и это также может представлять для нас интерес.

Когда наш веб-сайт может реализовывать push, веб-интерфейс становится на один шаг ближе к родному приложению.

Push-уведомления состоят из двух API:

  • Notifications API используется для отображения системных уведомлений.
  • Push API используется для обработки push-сообщений, отправляемых сервером.

Оба этих API основаны на Service Worker API, Service Worker отвечает на push-сообщения в фоновом режиме и передает их приложению.

Notification

Получать разрешение

Перед созданием уведомления необходимо получить разрешение пользователя:

// main.js
Notification.requestPermission(function(status) {
    console.log('Notification permission status:', status);
    //status 会有三个取值default granted denied 分别代表: 默认值(每次访问页面都询问)、 允许、拒绝
});

Добавить уведомление

После получения разрешения пользователя вы можетеshowNotification()способ ограничения уведомлений из основного приложения.

// main.js
function displayNotification() {
  if (Notification.permission == 'granted') {
    navigator.serviceWorker.getRegistration().then(function(reg) {
      reg.showNotification('Hello world!');
    });
  }
}

обращать вниманиеshowNotification, вызовите этот метод для объекта регистрации Service Worker. Уведомления будут создаваться в активном Service Worker для прослушивания событий, вызванных взаимодействием с уведомлением.

showNotificationМетоды имеют необязательные параметрыoptions, чтобы настроить уведомления.

// main.js
function displayNotification() {
  if (Notification.permission == 'granted') {
    navigator.serviceWorker.getRegistration().then(function(reg) {
      var options = {
        body: 'Here is a notification body!', // 对通知添加描述
        icon: 'images/example.png', // 添加一个icon图像
        vibrate: [100, 50, 100], // 指定通知的电话振动模式,手机将振动100ms,暂停50ms,再次振动100ms
        data: {
          dateOfArrival: Date.now(),
          primaryKey: 1
        }, // 给通知添加自定义数据,当监听到通知的时候,可以捕获到这些数据,方便使用。
        actions: [
          {action: 'explore', title: 'Explore this new world',
            icon: 'images/checkmark.png'},
          {action: 'close', title: 'Close notification',
            icon: 'images/xmark.png'},
        ] // 自定义的操作
     };
      reg.showNotification('Hello world!', options);
    });
  }
}

прослушать событие

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

// service-worker.js
self.addEventListener('notificationclick', function(e) {
  var notification = e.notification;
  var primaryKey = notification.data.primaryKey;
  var action = e.action;

  if (action === 'close') {
    notification.close();
  } else {
    clients.openWindow('http://www.example.com');
    notification.close();
  }
});

Push

Операция уведомления должна сочетаться с push-уведомлением для достижения взаимодействия с пользователем, активного уведомления и напоминания пользователю.

Push service

В каждом браузере естьpush service(служба push-уведомлений), когда пользователь разрешает разрешение на отправку текущего веб-сайта, текущий веб-сайт может быть подписан наpush service. Это создает объект контракта, который содержит конечную точку службы push и открытые ключи. При отправке push-сообщения оно будет отправлено на URL-адрес конечной точки и зашифровано с помощью открытого ключа.push serviceбудет отправлен правильному клиенту.

Как служба push узнает, какому клиенту отправить сообщение? URL-адрес конечной точки содержит уникальный идентификатор. Этот идентификатор используется для перенаправления ваших сообщений на нужное устройство и при обработке браузером для идентификации Service Worker, который должен обрабатывать запрос.

Push-уведомления и сервис-воркеры работают вместе, поэтому push-уведомления также должны быть HTTPS, что гарантирует безопасность связи между сервером и службой push-уведомлений, а также безопасность службы push-уведомлений для пользователя.

Однако HTTPS не гарантирует безопасность самой службы push-уведомлений. Мы должны гарантировать, что данные, отправляемые с сервера клиенту, не подделываются и не проверяются напрямую какой-либо третьей стороной. Поэтому сообщение на сервере должно быть зашифровано.

Весь процесс отправки и получения показов

На стороне клиента:

1. Подпишитесь на push-сервис

2. Отправьте объект подписки на сервер

На сервере:

1. Создайте данные, отправленные пользователям

2. Зашифровать данные с помощью открытого ключа пользователя.

3. URL-адрес конечной точки, на которую отправляются данные с использованием зашифрованных полезных данных.

Сообщение направляется на устройство пользователя. Разбудите браузер, найдите нужного сервис-воркера и вызовите push-событие.

1. Получить данные сообщения (если есть) в push-событии

2. Выполнение пользовательской логики в push-событиях

3. Показать уведомления

Обработка push-событий

Когда браузер, который поддерживает push-сообщения, получает сообщение, он отправляет обслуживающий рабочийpushмероприятие. Мы можем создать сервис-воркера вpushСлушатели событий для обработки сообщений:

// service-worker.js

self.addEventListener('push', function(e) {
  var options = {
    body: 'This notification was generated from a push!',
    icon: 'images/example.png',
    vibrate: [100, 50, 100],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: '2'
    },
    actions: [
      {action: 'explore', title: 'Explore this new world',
        icon: 'images/checkmark.png'},
      {action: 'close', title: 'Close',
        icon: 'images/xmark.png'},
    ]
  };
  e.waitUntil(
    self.registration.showNotification('Hello world!', options)
  );
});

Отличие от предыдущего заключается в том, что здесь отслеживается push-событие, ранее использовалось событие уведомления, а здесь используется метод event.waitUntil для продления жизненного цикла push-события до завершения асинхронной операции showNotification.

Подпишитесь на push-уведомления

Прежде чем отправлять push-сообщения, мы должны сначала подписаться на push-сервис. Подписка возвращает объект подписки илиsubscription. Это очень важная часть всего процесса, поэтому мы можем знать, куда отправляется push-уведомление.

// main.js
//检查是否订阅了
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('sw.js').then(function (reg) {
                console.log('Service Worker Registered!', reg);

                reg.pushManager.getSubscription().then(function (sub) {
                    if (sub === null) {
                        // Update UI to ask user to register for Push
                        console.log('Not subscribed to push service!');
                    } else {
                        // We have a subscription, update the database
                        console.log('Subscription object: ', sub);
                    }
                });
            })
            .catch(function (err) {
                console.log('Service Worker registration failed: ', err);
            });
    }

    function subscribeUser() {
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.ready.then(function (reg) {

                reg.pushManager.subscribe({
                    userVisibleOnly: true
                }).then(function (sub) {
                    console.log('Endpoint URL: ', sub.endpoint);
                }).catch(function (e) {
                    if (Notification.permission === 'denied') {
                        console.warn('Permission for notifications was denied');
                    } else {
                        console.error('Unable to subscribe to push', e);
                    }
                });
            })
        }
    }

Протокол веб-передачи

Протокол Web Push является официальным стандартом для отправки push-сообщений в браузеры. В нем описывается структура и процесс создания push-сообщения, его шифрования и отправки на платформу push-сообщений. Протокол абстрагируется от сведений о том, какая платформа обмена сообщениями и браузер у пользователя.

Протокол Web Push сложен, но нам не нужно знать все детали. Браузер автоматически отвечает за подписку пользователей с помощью службы push. Наша работа как разработчиков состоит в том, чтобы получить токен подписки, извлечь URL-адрес и отправить туда сообщение.

{"endpoint":"https://fcm.googleapis.com/fcm/send/dpH5lCsTSSM:APA91bHqjZxM0VImWWqDRN7U0a3AycjUf4O-byuxb_wJsKRaKvV_iKw56s16ekq6FUqoCF7k2nICUpd8fHPxVTgqLunFeVeB9lLCQZyohyAztTH8ZQL9WCxKpA6dvTG_TUIhQUFq_n",
"keys": {
    "p256dh":"BLQELIDm-6b9Bl07YrEuXJ4BL_YBVQ0dvt9NQGGJxIQidJWHPNa9YrouvcQ9d7_MqzvGS9Alz60SZNCG3qfpk=",
    "auth":"4vQK-SvRAN5eo-8ASlrwA=="
    }
}

Обычно аутентификация VSPID используется для идентификации личности. Непосредственно к предыдущему примеру

//main.js
    var endpoint;
    var key;
    var authSecret;

    // We need to convert the VAPID key to a base64 string when we subscribe
    function urlBase64ToUint8Array(base64String) {
      const padding = '='.repeat((4 - base64String.length % 4) % 4);
      const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

      const rawData = window.atob(base64);
      const outputArray = new Uint8Array(rawData.length);

      for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
      }
      return outputArray;
    }

    function determineAppServerKey() {
      var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY';
      return urlBase64ToUint8Array(vapidPublicKey);
    }

    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('sw.js').then(function (registration) {

        return registration.pushManager.getSubscription()
          .then(function (subscription) {

            if (subscription) {
              // We already have a subscription, let's not add them again
              return;
            }

            return registration.pushManager.subscribe({
                userVisibleOnly: true,
                applicationServerKey: determineAppServerKey()
              })
              .then(function (subscription) {

                var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
                key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
                var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
                authSecret = rawAuthSecret ?
                  btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';

                endpoint = subscription.endpoint;

                return fetch('http://localhost:3111/register', {
                  method: 'post',
                  headers: new Headers({
                    'content-type': 'application/json'
                  }),
                  body: JSON.stringify({
                    endpoint: subscription.endpoint,
                    key: key,
                    authSecret: authSecret,
                  }),
                })

              });
          });
      }).catch(function (err) {
        // registration failed :(
        console.log('ServiceWorker registration failed: ', err);
      });
    }

// server.js

const webpush = require('web-push');
const express = require('express');
var bodyParser = require('body-parser');
var path = require('path');
const app = express();

// Express setup
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({     // to support URL-encoded bodies
  extended: true
}));

function saveRegistrationDetails(endpoint, key, authSecret) {
  // Save the users details in a DB
}

webpush.setVapidDetails(
  'mailto:contact@deanhume.com',
  'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY',
  'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'
);

// Send a message
app.post('/sendMessage', function (req, res) {

  var endpoint = req.body.endpoint;
  var authSecret = req.body.authSecret;
  var key = req.body.key;

  const pushSubscription = {
    endpoint: req.body.endpoint,
    keys: {
      auth: authSecret,
      p256dh: key
    }
  };

  var body = 'Breaking News: Nose picking ban for Manila police';
  var iconUrl = 'https://raw.githubusercontent.com/deanhume/progressive-web-apps-book/master/chapter-6/push-notifications/public/images/homescreen.png';

  webpush.sendNotification(pushSubscription,
    JSON.stringify({
      msg: body,
      url: 'http://localhost:3111/article?id=1',
      icon: iconUrl,
      type: 'actionMessage'
    }))
    .then(result => {
      console.log(result);
      res.sendStatus(201);
    })
    .catch(err => {
      console.log(err);
    });
});

// Register the user
app.post('/register', function (req, res) {

  var endpoint = req.body.endpoint;
  var authSecret = req.body.authSecret;
  var key = req.body.key;

  // Store the users registration details
  saveRegistrationDetails(endpoint, key, authSecret);

  const pushSubscription = {
    endpoint: req.body.endpoint,
    keys: {
      auth: authSecret,
      p256dh: key
    }
  };

  var body = 'Thank you for registering';
  var iconUrl = '/images/homescreen.png';

  webpush.sendNotification(pushSubscription,
    JSON.stringify({
      msg: body,
      url: 'https://localhost:3111',
      icon: iconUrl,
      type: 'register'
    }))
    .then(result => {
      console.log(result);
      res.sendStatus(201);
    })
    .catch(err => {
      console.log(err);
    });

});

// The server
app.listen(3111, function () {
  console.log('Example app listening on port 3111!')
});

Весь процесс push будет подробно описан позже. Вы также можете сначала увидеть описание учебника, предоставленное Google.Developers.Google.com/Web/iTwo/Fatboy…

Ссылаться на

Progressive Web Apps Training

App Shell

Service Workers: an Introduction

MDN Progressive web apps

MDN WorkerGlobalScope [1]

The Service Worker Lifecycle

W3C IndexedDB API 3.0

Introduction to Push Notifications