사용자 구독

첫 번째 단계는 사용자에게 푸시 메시지를 보낼 권한을 얻는 것입니다. 그러면 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 브라우저 호환성

브라우저 지원

  • 42
  • 17
  • 44
  • 16

소스

권한 요청

서비스 워커를 등록했으며 사용자를 구독할 준비가 되었습니다. 다음 단계는 사용자로부터 푸시 메시지를 보낼 권한을 얻는 것입니다.

권한을 얻기 위한 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 옵션

브라우저에 푸시가 처음 추가되었을 때는 개발자가 푸시 메시지를 보내고 알림을 표시하지 않아야 하는지에 대한 확신이 없었습니다. 이는 일반적으로 사용자가 백그라운드에서 무언가가 발생했다는 사실을 알지 못하기 때문에 자동 푸시라고 합니다.

문제는 개발자가 사용자 모르게 사용자의 위치를 지속적으로 추적하는 등 악의적인 작업을 할 수 있다는 것이었습니다.

이러한 시나리오를 피하고 사양 작성자에게 이 기능을 가장 잘 지원하는 방법을 고려할 시간을 주기 위해 userVisibleOnly 옵션이 추가되었으며 true의 값을 전달하는 것은 푸시가 수신될 때마다 (즉, 자동 푸시 없음) 알림을 웹 앱이 표시한다는 브라우저와의 심볼릭 계약입니다.

현재 true 값을 전달해야 합니다. userVisibleOnly 키를 포함하지 않거나 false를 전달하지 않으면 다음과 같은 오류가 발생합니다.

Chrome은 현재 사용자에게 표시되는 메시지가 표시되는 구독의 경우에만 Push API를 지원합니다. 대신 pushManager.subscribe({userVisibleOnly: true})를 호출하여 이를 나타낼 수 있습니다. 자세한 내용은 https://goo.gl/yqv4Q4를 참조하세요.

현재로서는 포괄적 자동 푸시가 Chrome에 구현되지 않을 것으로 보입니다. 대신 사양 작성자는 웹 앱 사용에 따라 웹 앱에 특정 수의 자동 푸시 메시지를 허용하는 예산 API의 개념을 살펴보고 있습니다.

applicationServerKey 옵션

이전 섹션에서 '애플리케이션 서버 키'에 대해 간단히 언급했습니다. '애플리케이션 서버 키'는 푸시 서비스에서 사용자를 구독하는 애플리케이션을 식별하고 동일한 애플리케이션이 해당 사용자에게 메시지를 전송하는지 확인하는 데 사용됩니다.

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

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

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

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

공개 애플리케이션 서버 키가 구독 메서드에 사용되는 그림

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

메시지를 보낼 때 비공개 애플리케이션 서버 키가 사용되는 방법

엄밀히 말해 applicationServerKey는 선택사항입니다. 그러나 Chrome에서 가장 쉽게 구현하는 방법은 이 기능이 필요하며, 다른 브라우저에서도 이 기능이 필요할 수 있습니다. Firefox에서는 선택사항입니다.

애플리케이션 서버 키가 있어야 하는 내용을 정의하는 사양은 VAPID 사양입니다. '애플리케이션 서버 키' 또는 'VAPID 키'와 관련된 내용을 읽을 때마다 항상 같은 키임을 기억하세요.

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

web-push-codelab.glitch.me를 방문하여 애플리케이션 서버 키의 공개 및 비공개 세트를 만들거나 웹-푸시 명령줄에서 다음과 같은 방법으로 키를 생성할 수 있습니다.

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

애플리케이션에서 이러한 키는 한 번만 만들면 되며 비공개 키는 비공개로 유지해야 합니다. (네, 방금 말한 거네요.)

권한 및subscribe()

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입니다. 푸시 메시지를 트리거하려면 이 URL에 대한 POST 요청을 실행합니다.

keys 객체에는 푸시 메시지와 함께 전송되는 메시지 데이터를 암호화하는 데 사용되는 값이 포함되어 있습니다. 이에 대해서는 이 섹션의 뒷부분에서 설명합니다.

서버에 구독 보내기

푸시 구독이 있으면 이를 서버로 전송해야 합니다. 방법은 개발자가 선택하지만 한 가지 팁은 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 세부정보를 사용하면 원할 때마다 사용자에게 메시지를 보낼 수 있습니다.

FAQ

이 시점에서 사람들이 묻는 몇 가지 일반적인 질문은 다음과 같습니다.

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

아니요. 푸시 서비스는 브라우저에 의해 선택되며 subscribe() 호출에서 확인한 바와 같이 브라우저는 푸시 서비스에 네트워크 요청을 보내 PushSubscription을 구성하는 세부정보를 가져옵니다.

브라우저마다 다른 푸시 서비스를 사용하는데 API가 서로 다르지 않나요?

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

이 공통 API를 웹 푸시 프로토콜이라고 하며 푸시 메시지를 트리거하기 위해 애플리케이션에서 실행해야 하는 네트워크 요청을 설명합니다.

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

아니요. 사용자는 메시지를 수신하려는 각 브라우저에서 푸시에 등록해야 합니다. 이렇게 하려면 사용자가 각 기기에 권한을 부여해야 합니다.

다음에 수행할 작업

Codelab