Как подписать пользователя на push-уведомления

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

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

Обнаружение функций

Сначала проверьте, поддерживает ли браузер 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. Если файл выполняется без ошибок, объект Promise, возвращенный методом register(), преобразуется. Если возникают ошибки, объект возвращает отказ.

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

Когда register() разрешается, возвращается ServiceWorkerRegistration. Эта регистрация используется для доступа к PushManager API.

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

Browser Support

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 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'.

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

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

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

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

Как подписать пользователя с помощью 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-сообщения, разработчики не были уверены, стоит ли отправлять их без показа уведомления. Такие уведомления часто называют тихими, поскольку пользователь не знает, что в фоновом режиме произошло событие.

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

Чтобы избежать этого и дать авторам спецификаций возможность подумать, как лучше всего реализовать поддержку этой функции, был добавлен параметр 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.

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

Параметр applicationServerKey

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

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

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

На схеме ниже показано, как это происходит.

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

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

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

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

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

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

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

Вы можете создать набор открытых и закрытых ключей сервера приложений, используя командную строку 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-уведомления не поступают в течение длительного времени или если браузер обнаруживает, что пользователь не использует приложение, у которого есть разрешение на уведомления. Чтобы этого не происходило, можно повторно подписывать пользователя при каждом получении уведомления, как показано в следующем фрагменте кода. Для этого вам нужно отправлять уведомления достаточно часто, чтобы браузер не отменял подписку автоматически. Вам следует тщательно взвесить преимущества и недостатки отправки уведомлений, чтобы не заспамить пользователя только ради того, чтобы он не забыл продлить подписку. Не пытайтесь обойти меры, которые браузер принимает для защиты пользователей от давно забытых подписок на уведомления.

/* 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-уведомлений в каждом браузере, в котором он хочет их получать. При этом пользователь должен предоставить разрешение на каждом устройстве.

Дальнейшие действия

Практические работы