Подписка пользователя

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

API JavaScript для этого достаточно прост, поэтому давайте пройдемся по логической схеме.

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

  1. Проверьте наличие serviceWorker в навигаторе .
  2. Проверьте наличие PushManager в окне .
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;
}

Несмотря на то, что поддержка браузеров как для Service Worker, так и для 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 DevTools.

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

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

Поддержка браузера

  • Хром: 42.
  • Край: 17.
  • Фаерфокс: 44.
  • Сафари: 16.

Источник

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

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

Давайте посмотрим на все варианты, которые мы можем передать.

Параметры userVisibleOnly

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

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

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

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

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

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

опция applicationServerKey

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

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

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

Диаграмма ниже иллюстрирует эти шаги.

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

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

Если позже вы захотите отправить push-сообщение, вам потребуется создать заголовок авторизации , который будет содержать информацию, подписанную закрытым ключом вашего сервера приложений. Когда служба push-уведомлений получает запрос на отправку push-сообщения, она может проверить этот подписанный заголовок авторизации , найдя открытый ключ, связанный с конечной точкой, получающей запрос. Если подпись действительна, служба 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() , браузер запросит у вас разрешения. Это полезно, если ваш пользовательский интерфейс работает с этим потоком, но если вы хотите большего контроля (а я думаю, что большинство разработчиков так и сделают), придерживайтесь 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-уведомления вы часто получаете 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);
    });
}

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

Если у вас есть принудительная подписка, вы захотите отправить ее на свой сервер. Как это сделать, решать вам, но небольшой совет: используйте 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.');
      }
    });
}

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

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

Куда идти дальше

Лаборатории кода