사용자 구독

첫 번째 단계는 사용자로부터 푸시 메시지를 보낼 권한을 받는 것입니다. 그런 다음 PushSubscription을 사용해 보세요.

이 작업을 수행하는 JavaScript API는 상당히 간단하므로 논리 흐름을 통해

특성 감지

먼저, 현재 브라우저가 실제로 푸시 메시지를 지원하는지 확인해야 합니다. 그러면 푸시는 두 가지 간단한 검사로 지원됩니다

  1. navgator에서 serviceWorker를 확인합니다.
  2. window에서 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;
}

브라우저 지원이 서비스 워커와 이때 특성 탐지와 푸시 메시지 모두에 대해 점진적으로 개선해야 합니다.

서비스 워커 등록

기능 감지를 통해 서비스 워커와 푸시가 모두 지원된다는 것을 알 수 있습니다. 다음 단계 '등록'입니다. 서비스 워커를 살펴보겠습니다.

서비스 워커를 등록할 때 브라우저에 서비스 워커 파일의 위치를 알려줍니다. 파일은 여전히 JavaScript이지만 브라우저에서 '액세스 권한을 부여'합니다. 서비스 워커에 대해 API(푸시 포함) 더 정확하게 말하면, 브라우저는 파일을 서비스 워커에서 실행합니다. 환경입니다

서비스 워커를 등록하려면 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()가 거부되면 Chrome DevTools에서 JavaScript에 오타 / 오류가 있는지 다시 확인합니다.

register()가 확인되면 ServiceWorkerRegistration이 반환됩니다. 여기서는 등록을 통해 PushManager API에 액세스할 수 있습니다.

PushManager API 브라우저 호환성

브라우저 지원

  • Chrome: 42. <ph type="x-smartling-placeholder">
  • Edge: 17. <ph type="x-smartling-placeholder">
  • Firefox: 44. <ph type="x-smartling-placeholder">
  • Safari: 16. <ph type="x-smartling-placeholder">

소스

권한 요청

서비스 워커를 등록했고 사용자를 구독할 준비가 되었습니다. 다음 단계는 사용자에게 푸시 메시지를 보낼 수 있는 권한을 부여합니다.

권한을 얻기 위한 API는 비교적 간단하며, 단점은 API가 최근에 콜백에서 프로미스 반환으로 변경되었습니다. 이 문제는 현재 API가 구현된 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 옵션

브라우저에 푸시가 처음 추가되었을 때, 개발자가 푸시 메시지를 보낼 수 있고 알림을 표시하지 않을 수 있습니다. 일반적으로 이를 무음이라고 합니다. 사용자가 백그라운드에서 무언가가 발생했다는 것을 모르기 때문입니다.

문제는 개발자가 모바일 웹 사이트에서 사용자의 위치를 추적하는 등 악의적인 행위를 할 수 있다는 것이었습니다. 사용자 모르게 지속적으로 업데이트됩니다.

이러한 시나리오를 피하고 사양 작성자에게 이를 지원하는 최선의 방법을 고려할 시간을 주기 위해 기능에서 userVisibleOnly 옵션이 추가되었으며 true 값을 전달하는 것은 기호입니다. 푸시가 실행될 때마다 웹 앱이 알림을 표시한다는 데 동의함을 알림을 수신하면 됩니다 (즉, 자동 푸시가 없음).

현재는 true 값을 전달해야 합니다. 만약 userVisibleOnly 키를 누르거나 false를 전달하면 다음 오류가 발생합니다.

Chrome은 현재 메시지가 표시됩니다. 다음을 호출하여 이를 나타낼 수 있습니다. 대신 pushManager.subscribe({userVisibleOnly: true})하세요. 자세한 내용은 자세한 내용은 https://goo.gl/yqv4Q4를 참조하세요.

현재는 포괄적 자동 푸시가 Chrome에서 구현되지 않는 것으로 보입니다. 대신 사양 개발자들은 웹 앱이 일정 시간 이상 자동 푸시 메시지 수.

applicationServerKey 옵션

"애플리케이션 서버 키" 이전 섹션에 나와 있습니다. "애플리케이션 서버 키' 푸시 서비스에서 사용자를 구독하는 애플리케이션을 식별하는 데 사용되며 동일한 애플리케이션이 해당 사용자에게 메시지를 보내는지 확인합니다.

애플리케이션 서버 키는 애플리케이션에 고유한 공개 키 및 비공개 키 쌍입니다. 비공개 키는 애플리케이션에 비밀로 유지해야 하며, 공개 키는 자유롭게.

subscribe() 호출에 전달된 applicationServerKey 옵션은 애플리케이션의 공개 옵션입니다. 키를 누릅니다. 브라우저는 사용자를 구독할 때 이것을 푸시 서비스로 전달합니다. 즉, 서비스는 애플리케이션의 공개 키를 사용자의 PushSubscription에 연결할 수 있습니다.

아래의 다이어그램은 이러한 단계를 보여줍니다.

  1. 웹 앱이 브라우저에 로드되고 subscribe()를 호출하여 공개 객체를 전달합니다. 애플리케이션 서버 키입니다.
  2. 그런 다음 브라우저가 푸시 서비스에 네트워크 요청을 하고, 푸시 서비스는 엔드포인트를 생성합니다. 이 엔드포인트를 애플리케이션 공개 키와 연결하고 있습니다.
  3. 브라우저는 이 엔드포인트를 PushSubscription에 추가하며 이는 다음을 통해 반환됩니다. subscribe() 프라미스

구독에 사용되는 공개 애플리케이션 서버 키를 보여주는 삽화
메서드를 사용하여 축소하도록 요청합니다.

나중에 푸시 메시지를 보내려면 Authorization 헤더를 만들어야 합니다. 여기에는 애플리케이션 서버의 비공개 키로 서명된 정보가 포함됩니다. 이 푸시 서비스는 푸시 메시지 전송 요청을 수신하면 이 서명된 Authorization 헤더를 검증할 수 있습니다. 요청을 수신하는 엔드포인트에 연결된 공개 키를 조회합니다. 서명이 푸시 서비스는 일치하는 비공개 키를 찾습니다. 이것은 기본적으로 다른 사람이 이메일을 보내는 것을 막는 보안 조치입니다. 애플리케이션 사용자에게 보낼 수 있습니다

비공개 애플리케이션 서버 키가
메시지

기술적으로 applicationServerKey는 선택사항입니다. 하지만 가장 쉬운 방법은 구현 시 이 기능이 필요하며, 다른 브라우저에서는 생각해 보세요 Firefox에서는 선택사항입니다.

애플리케이션 서버 키가 무엇이어야 하는지 정의하는 사양은 VAPID 사양 "애플리케이션 서버 키" 또는 'VAPID 키': 같은 것임을 기억하세요.

애플리케이션 서버 키를 만드는 방법

다음 페이지를 방문하여 애플리케이션 서버 키의 공개 및 비공개 세트를 만들 수 있습니다. web-push-codelab.glitch.me 또는 web-push 명령줄 다음을 실행하여 키를 생성합니다.

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

애플리케이션에 대해 이러한 키를 한 번만 생성하면 되므로, 비공개 키를 사용합니다. (네, 방금 말한 거죠.)

권한 및 Subscription()

subscribe() 호출의 한 가지 부작용이 있습니다. 웹 앱에 subscribe()를 호출할 때 알림을 표시하면 브라우저에서 권한을 부여할 수 있습니다. 이는 UI가 이 흐름에서 작동하는 경우 유용하지만 대부분의 개발자는 이러한 컨트롤을 사용할 수 있으며 Notification.requestPermission() API를 고수하세요 사용할 수 있습니다

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 객체에는 푸시를 전송하는 데 필요한 모든 필수 정보가 포함되어 있습니다. 메시지를 보낼 수 있습니다 JSON.stringify()를 사용하여 콘텐츠를 출력하면 다음과 같이 표시됩니다. 있습니다.

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

endpoint는 푸시 서비스 URL입니다. 푸시 메시지를 트리거하려면 POST 요청을 실행합니다. 이 URL에 추가합니다.

keys 객체에는 푸시 메시지로 전송된 메시지 데이터를 암호화하는 데 사용되는 값이 포함됩니다. (이 섹션의 뒷부분에서 설명)

만료를 방지하기 위한 정기적인 정기 결제 재신청

푸시 알림을 구독하면 nullPushSubscription.expirationTime을 수신하게 되는 경우가 많습니다. 이론적으로는 정기 결제가 만료되지 않는 것입니다 (DOMHighResTimeStamp 수신 시와 달리 정기 결제 만료 시점을 정확히 알려줍니다). 하지만 실제로는 브라우저에서 구독이 만료되도록 하는 것이 일반적입니다. 예를 들어 더 오랜 시간 동안 푸시 알림을 받지 못했거나 브라우저에서 사용자가 푸시 알림 권한이 있는 앱을 사용하고 있지 않은 것을 감지하면 이 상태가 유지됩니다. 이를 방지하는 한 가지 패턴은 다음 스니펫에 나와 있는 것처럼 수신된 각 알림마다 사용자를 다시 구독하는 것입니다. 이렇게 하려면 브라우저가 구독이 자동으로 만료되지 않도록 충분히 자주 알림을 보내야 하며, 구독이 만료되지 않도록 비자발적으로 사용자에게 스팸을 보내는 것과 합법적인 알림 필요성의 장단점을 매우 신중하게 비교해야 합니다. 결국, 오랫동안 잊혀진 알림 구독으로부터 사용자를 보호하기 위해 브라우저에 싸우려고 해서는 안 됩니다.

/* 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 세부정보가 있으면 사용자에게 보낼 수 있습니다. 보낼 수 있습니다.

만료를 방지하기 위한 정기적인 정기 결제 재신청

푸시 알림을 구독하면 nullPushSubscription.expirationTime을 수신하게 되는 경우가 많습니다. 이론적으로는 정기 결제가 만료되지 않는 것입니다 (DOMHighResTimeStamp 수신 시와 달리 정기 결제 만료 시점을 정확히 알려줍니다). 그러나 실제로는 브라우저에서 구독이 만료되도록 하는 것이 일반적입니다. 예를 들어 오랫동안 푸시 알림을 받지 못했거나 브라우저에서 사용자가 푸시 알림 권한이 있는 앱을 사용하고 있지 않은 것을 감지하면 이 상태가 유지됩니다. 이를 방지하는 한 가지 패턴은 다음 스니펫에 나와 있는 것처럼 수신된 각 알림마다 사용자를 다시 구독하는 것입니다. 이렇게 하려면 브라우저가 구독이 자동으로 만료되지 않도록 알림을 자주 보내야 하며, 구독이 만료되지 않도록 사용자에게 스팸을 발송하는 것과 적법한 알림 요구사항의 장단점을 매우 신중하게 비교해야 합니다. 결국, 오랫동안 잊혀진 알림 구독으로부터 사용자를 보호하기 위해 브라우저에 싸우려고 해서는 안 됩니다.

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

FAQ

현재 많은 사람들이 궁금해하는 몇 가지 질문은 다음과 같습니다.

브라우저에서 사용하는 푸시 서비스를 변경할 수 있나요?

아니요. 푸시 서비스는 브라우저에 의해 선택되며 subscribe() 호출 시 브라우저에서 푸시 서비스에 네트워크 요청을 보냅니다. PushSubscription을 구성하는 세부정보를 검색합니다.

브라우저마다 다른 푸시 서비스를 사용합니다. API는 다르지 않나요?

모든 푸시 서비스에는 동일한 API가 필요합니다.

이러한 공통 API를 웹 푸시 프로토콜 네트워크 요청을 설명하는 애플리케이션에서 푸시 메시지를 트리거하기 위해 만들어야 하는 모든 정보입니다.

데스크톱에서 사용자를 구독하면 휴대전화에서도 구독되나요?

아니요. 사용자는 원하는 각 브라우저에서 푸시에 등록해야 합니다. 수신할 수 없습니다. 또한 각 기기에 대해 사용자가 권한을 부여하는 데 사용됩니다.

다음에 수행할 작업

Codelab