Подпишите пользователя на получение push-уведомлений.

Matt Gaunt

Для отправки push-уведомлений необходимо сначала получить разрешение пользователя, а затем подписать его устройство на службу push-уведомлений. Это включает в себя использование API JavaScript для получения объекта PushSubscription , который затем отправляется на ваш сервер.

API JavaScript позволяет легко управлять этим процессом. В этом руководстве описан весь процесс, включая определение функций, запрос разрешений и управление процессом подписки.

Обнаружение признаков

Для начала проверьте, поддерживает ли браузер push-уведомления. Проверить поддержку push-уведомлений можно двумя способами:

  • Проверьте наличие объекта serviceWorker в объекте navigator .
  • Проверьте наличие объекта PushManager в объекте window .
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

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

Зарегистрируйте работника сферы услуг

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

При регистрации сервис-воркера вы указываете браузеру местоположение файла сервис-воркера. Этот файл представляет собой JavaScript-файл, но браузер предоставляет ему доступ к API сервис-воркера, включая отправку push-уведомлений. В частности, браузер запускает файл в среде сервис-воркера.

Для регистрации сервис-воркера вызовите метод navigator.serviceWorker.register() и передайте путь к вашему файлу. Например:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

Эта функция сообщает браузеру местоположение файла сервис-воркера. В данном случае файл сервис-воркера находится по адресу /service-worker.js . После вызова register() браузер выполняет следующие действия:

  1. Загрузите файл сервис-воркера.

  2. Запустите JavaScript.

  3. Если файл выполняется корректно и без ошибок, промис, возвращаемый функцией register() разрешается. Если возникают ошибки, промис отклоняется.

Примечание : Если register() отклонен, проверьте JavaScript на наличие опечаток или ошибок в инструментах разработчика Chrome.

Когда register() завершается успешно, он возвращает объект ServiceWorkerRegistration . Вы используете эту регистрацию для доступа к API PushManager .

Совместимость API PushManager с браузерами

Browser Support

  • Chrome: 42.
  • Край: 17.
  • Firefox: 44.
  • Сафари: 16.

Source

Запрос разрешения

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

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

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

В приведенном выше коде вызов метода Notification.requestPermission() отображает пользователю запрос:

В браузерах Chrome на настольных компьютерах и мобильных устройствах отображается запрос на предоставление разрешений.

После того, как пользователь взаимодействует с запросом на разрешение, выбрав «Разрешить» , «Заблокировать» или закрыв его, вы получите результат в виде строки: 'granted' , 'default' или 'denied' .

В приведенном примере кода промис, возвращаемый функцией askPermission() , разрешается, если разрешение предоставлено; в противном случае он генерирует ошибку, и промис отклоняется.

Обработайте крайний случай, когда пользователь нажимает кнопку «Заблокировать» . В этом случае ваше веб-приложение не сможет повторно запросить у пользователя разрешение. Пользователю придется вручную разблокировать приложение, изменив состояние разрешений в панели настроек. Тщательно продумайте, когда и как запрашивать разрешение, потому что, если пользователь нажмет кнопку «Заблокировать» , отменить это решение будет непросто.

Большинство пользователей дают разрешение, если понимают, зачем приложение его запрашивает.

В этом документе далее обсуждается, как некоторые популярные сайты запрашивают разрешение.

Подпишите пользователя на PushManager

После регистрации вашего сервис-воркера и получения разрешения вы можете подписать пользователя, вызвав метод registration.pushManager.subscribe() .

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

При вызове метода subscribe() вы передаете объект options , состоящий из обязательных и необязательных параметров.

В этом разделе описаны варианты, которые вы можете пропустить.

параметры userVisibleOnly

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

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

Чтобы избежать подобного сценария и позволить авторам спецификаций обдумать наилучший способ поддержки этой функции, была добавлена ​​опция userVisibleOnly . Передача значения true символизирует соглашение с браузером о том, что веб-приложение будет отображать уведомление каждый раз, когда получает push-уведомление (то есть, никаких бесшумных push-уведомлений).

Необходимо передать значение true . Если вы не укажете ключ userVisibleOnly или передадите false , вы получите следующую ошибку:

Chrome currently only supports the Push API for subscriptions that will result
in user-visible messages. You can indicate this by calling
`pushManager.subscribe({userVisibleOnly: true})` instead. See
[https://goo.gl/yqv4Q4](https://goo.gl/yqv4Q4) for more details.

Chrome поддерживает Push API только для подписок, которые приводят к появлению видимых пользователю сообщений. Укажите это, вызвав метод pushManager.subscribe({userVisibleOnly: true}) . Для получения дополнительной информации см. https://goo.gl/yqv4Q4 .

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

опция applicationServerKey

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

Ключи сервера приложений представляют собой пару открытого и закрытого ключей, уникальных для вашего приложения. Храните закрытый ключ в секрете от вашего приложения, а открытый ключ свободно распространяйте.

Параметр applicationServerKey , передаваемый в вызов subscribe() представляет собой открытый ключ вашего приложения. Браузер передает этот ключ службе push-уведомлений при подписке пользователя, что позволяет службе push-уведомлений связать открытый ключ вашего приложения с подпиской PushSubscription пользователя.

Следующая диаграмма иллюстрирует эти шаги.

  1. Загрузите ваше веб-приложение в браузере и вызовите метод subscribe() , передав ему ваш открытый ключ сервера приложений.
  2. Затем браузер отправляет сетевой запрос в службу push-уведомлений, которая генерирует конечную точку, связывает эту конечную точку с открытым ключом вашего приложения и возвращает конечную точку браузеру.
  3. Браузер добавляет эту конечную точку в объект PushSubscription , который возвращает промис subscribe() .

Диаграмма, иллюстрирующая использование открытого ключа сервера приложений в методе `subscribe()`.

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

Диаграмма, иллюстрирующая использование закрытого ключа сервера приложений при отправке сообщения.

Технически, applicationServerKey является необязательным. Однако простейшая реализация в Chrome требует его наличия, и другие браузеры могут потребовать его в будущем. В Firefox он необязателен.

Спецификация VAPID определяет ключ сервера приложений. Когда вы видите ссылки на ключи сервера приложений или ключи VAPID, помните, что это одно и то же.

Создание ключей сервера приложений

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

    $ npm install -g web-push
    $ web-push generate-vapid-keys

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

Разрешения и subscribe()

Вызов метода subscribe() имеет один побочный эффект. Если ваше веб-приложение не имеет разрешения на отображение уведомлений при вызове subscribe() , браузер запросит эти разрешения за вас. Это полезно, если ваш пользовательский интерфейс работает с таким потоком, но если вам нужен больший контроль (что нужно большинству разработчиков), используйте API Notification.requestPermission() , который обсуждался ранее в этом документе.

Обзор PushSubscription

Вы вызываете subscribe() , передаете параметры и получаете промис, который разрешается в объект PushSubscription . Например:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Объект PushSubscription содержит всю информацию, необходимую для отправки push-уведомлений этому пользователю. Если вы распечатаете его содержимое с помощью JSON.stringify() , вы увидите следующее:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint — это URL-адрес службы push-уведомлений. Чтобы отправить push-уведомление, отправьте POST-запрос на этот URL-адрес.

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

Отправьте подписку на ваш сервер

После создания push-подписки отправьте её на свой сервер. Способ отправки вы определяете сами, но совет: используйте JSON.stringify() для извлечения всех необходимых данных из объекта подписки. В качестве альтернативы, вы можете вручную сформировать тот же результат, например:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

Для отправки подписки с веб-страницы используйте следующий код:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Сервер Node.js получает этот запрос и сохраняет данные в базу данных для последующего использования.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
    .then(function (subscriptionId) {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify({data: {success: true}}));
    })
    .catch(function (err) {
      res.status(500);
      res.setHeader('Content-Type', 'application/json');
      res.send(
        JSON.stringify({
          error: {
            id: 'unable-to-save-subscription',
            message:
              'The subscription was received but we were unable to save it to our database.',
          },
        }),
      );
    });
});

Имея данные PushSubscription на вашем сервере, вы можете отправлять сообщения пользователям в любое время.

Регулярно продлевайте подписку, чтобы она не истекла.

При подписке на push-уведомления вы часто получаете значение PushSubscription.expirationTime , равное null . Теоретически это означает, что подписка никогда не истекает. (В отличие от этого, DOMHighResTimeStamp указывает точное время истечения срока действия.) Однако на практике браузеры часто позволяют подпискам истекать. Например, это может произойти, если push-уведомления не поступают в течение длительного времени или если браузер обнаруживает, что пользователь не использует приложение, имеющее разрешение на отправку push-уведомлений. Один из способов предотвратить это — повторно подписывать пользователя при каждом полученном уведомлении, как показано в следующем фрагменте кода. Это требует отправки уведомлений достаточно часто, чтобы предотвратить автоматическое истечение срока действия подписки браузером. Вам следует тщательно взвесить преимущества и недостатки законных потребностей в уведомлениях по сравнению с непроизвольной рассылкой спама пользователю исключительно для предотвращения истечения срока действия подписки. В конечном итоге, вы не должны пытаться обойти меры браузера по защите пользователя от давно забытых подписок на уведомления.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Часто задаваемые вопросы

Вот несколько часто задаваемых вопросов:

Можно ли изменить службу push-уведомлений, используемую браузером?

Нет, браузер выбирает службу push-уведомлений. Как обсуждалось в этом документе в связи с вызовом функции subscribe() , браузер отправляет сетевые запросы к службе push-уведомлений для получения сведений, составляющих PushSubscription .

Используют ли разные сервисы push-уведомлений разные API?

Все push-сервисы ожидают один и тот же API.

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

Если вы подписываете пользователя на компьютере, будет ли он подписан и на своем телефоне?

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

Следующие шаги

Кодлабс