오픈 웹의 푸시 알림

맷 곤트

개발자에게 웹에서 어떤 휴대기기 기능이 누락되었는지 물어보면 푸시 알림이 항상 가장 높은 순위에 오르게 됩니다.

푸시 알림을 사용하면 사용자가 좋아하는 사이트에서 시기적절한 업데이트를 수신하도록 선택할 수 있으며, 개발자는 참여도 높은 맞춤설정 콘텐츠를 통해 사용자의 재참여를 효과적으로 유도할 수 있습니다.

Chrome 버전 42부터 개발자는 Push APINotification API를 사용할 수 있습니다.

Chrome의 Push API는 웹 앱 매니페스트서비스 워커를 포함한 몇 가지 기술에 의존합니다. 이 게시물에서는 이러한 각 기술을 살펴보지만 푸시 메시지를 실행하고 실행하는 데 필요한 최소한의 기술만 살펴보겠습니다. 매니페스트의 다른 기능과 서비스 워커의 오프라인 기능을 더 잘 이해하려면 위 링크를 확인하세요.

또한 Chrome의 향후 버전에서 API에 추가될 사항을 살펴보고 마지막으로 FAQ를 제공합니다.

Chrome용 푸시 메시지 구현

이 섹션에서는 웹 앱에서 메시지 푸시를 지원하기 위해 완료해야 하는 각 단계를 설명합니다.

서비스 워커 등록

웹용 푸시 메시지를 구현하기 위해서는 서비스 워커가 있어야 합니다. 푸시 메시지를 수신하면 브라우저가 서비스 워커를 시작하여 페이지가 열려 있지 않아도 백그라운드에서 실행되는 서비스 워커를 시작하여 해당 푸시 메시지를 처리하는 방법을 결정할 수 있도록 이벤트를 전달할 수 있기 때문입니다.

다음은 웹 앱에 서비스 워커를 등록하는 방법을 보여주는 예입니다. 등록이 성공적으로 완료되면 initialiseState()를 호출하며 이에 대해서는 곧 다룹니다.

var isPushEnabled = false;

…

window.addEventListener('load', function() {
    var pushButton = document.querySelector('.js-push-button');
    pushButton.addEventListener('click', function() {
    if (isPushEnabled) {
        unsubscribe();
    } else {
        subscribe();
    }
    });

    // Check that service workers are supported, if so, progressively
    // enhance and add push messaging support, otherwise continue without it.
    if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js')
    .then(initialiseState);
    } else {
    console.warn('Service workers aren\'t supported in this browser.');
    }
});

버튼 클릭 핸들러는 사용자의 푸시 메시지를 구독하거나 구독 취소합니다. isPushEnabled는 푸시 메시지를 현재 구독 중인지만 추적하는 전역 변수입니다. 이러한 정보는 코드 스니펫 전체에서 참조됩니다.

그런 다음 푸시 메시지를 처리하는 로직이 있는 service-worker.js 파일을 등록하기 전에 서비스 워커가 지원되는지 확인합니다. 여기에서는 이 JavaScript 파일이 사이트의 서비스 워커임을 브라우저에 알리는 것입니다.

초기 상태 설정

Chrome에서 사용 설정 및 사용 중지된 푸시 메시지 UX의 예

서비스 워커가 등록되면 UI의 상태를 설정해야 합니다.

사용자는 사이트의 푸시 메시지를 사용 설정하거나 중지할 수 있는 간단한 UI를 기대하며 변경사항이 발생하는 경우 이를 최신 상태로 유지할 수 있습니다. 즉, 사이트에서 푸시 메시지를 사용 설정한 후 일주일 후에 사이트를 떠났다가 다시 돌아오면 UI에서 푸시 메시지가 이미 사용 설정되었음을 강조표시합니다.

이 문서의 UX 가이드라인을 확인할 수 있으며 이 도움말에서는 기술적인 측면에 중점을 둡니다.

이 시점에서는 사용 설정 또는 사용 중지의 두 가지 상태만 처리한다고 생각할 수 있습니다. 그러나 알림과 관련된 몇 가지 다른 상태도 고려해야 합니다.

Chrome의 여러 고려사항과 푸시 상태를 보여주는 다이어그램

버튼을 사용 설정하기 전에 확인해야 할 API가 많이 있습니다. 모든 것이 지원된다면 UI를 사용 설정하고 초기 상태를 설정하여 푸시 메시지의 구독 여부를 나타낼 수 있습니다.

이러한 검사 대부분의 경우 UI가 사용 중지되므로 초기 상태를 사용 중지로 설정해야 합니다. 이렇게 하면 페이지의 자바스크립트에 문제가 있을 때(예: JS 파일을 다운로드할 수 없거나 사용자가 자바스크립트를 사용 중지한 경우) 혼란을 방지할 수 있습니다.

<button class="js-push-button" disabled>
    Enable Push Messages
</button>

이 초기 상태를 사용하면 서비스 워커가 등록된 후 initialiseState() 메서드에서 위에 설명된 검사를 실행할 수 있습니다.

// Once the service worker is registered set the initial state
function initialiseState() {
    // Are Notifications supported in the service worker?
    if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
    console.warn('Notifications aren\'t supported.');
    return;
    }

    // Check the current Notification permission.
    // If its denied, it's a permanent block until the
    // user changes the permission
    if (Notification.permission === 'denied') {
    console.warn('The user has blocked notifications.');
    return;
    }

    // Check if push messaging is supported
    if (!('PushManager' in window)) {
    console.warn('Push messaging isn\'t supported.');
    return;
    }

    // We need the service worker registration to check for a subscription
    navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    // Do we already have a push message subscription?
    serviceWorkerRegistration.pushManager.getSubscription()
        .then(function(subscription) {
        // Enable any UI which subscribes / unsubscribes from
        // push messages.
        var pushButton = document.querySelector('.js-push-button');
        pushButton.disabled = false;

        if (!subscription) {
            // We aren't subscribed to push, so set UI
            // to allow the user to enable push
            return;
        }

        // Keep your server in sync with the latest subscriptionId
        sendSubscriptionToServer(subscription);

        // Set your UI to show they have subscribed for
        // push messages
        pushButton.textContent = 'Disable Push Messages';
        isPushEnabled = true;
        })
        .catch(function(err) {
        console.warn('Error during getSubscription()', err);
        });
    });
}

이 단계에 대한 간략한 개요는 다음과 같습니다.

  • ServiceWorkerRegistration 프로토타입에서 showNotification를 사용할 수 있는지 확인합니다. 이것이 없으면 푸시 메시지를 수신할 때 서비스 워커의 알림을 표시할 수 없습니다.
  • 현재 Notification.permission가 무엇인지 확인하여 "denied"가 아닌지 확인합니다. 권한이 거부되었다는 것은 사용자가 브라우저에서 권한을 수동으로 변경할 때까지 알림을 표시할 수 없음을 의미합니다.
  • 푸시 메시지가 지원되는지 확인하려면 창 객체에서 PushManager를 사용할 수 있는지 확인합니다.
  • 마지막으로 pushManager.getSubscription()를 사용하여 이미 정기 결제가 있는지 확인했습니다. 이를 통해 Google은 올바른 정보를 보유하고 있는지 확인하기 위해 정기 결제 세부정보를 서버로 전송하고 푸시 메시지가 이미 사용 설정되었는지 여부를 나타내도록 UI를 설정합니다. 이 도움말의 뒷부분에서 정기 결제 객체에 어떤 세부정보가 있는지 살펴보겠습니다.

구독 여부를 확인하고 푸시 버튼을 사용 설정하기 위해 navigator.serviceWorker.ready가 확인될 때까지 기다립니다. 서비스 워커가 활성화된 후에만 푸시 메시지를 실제로 구독할 수 있기 때문입니다.

다음 단계는 사용자가 푸시 메시지를 사용 설정하려고 할 때 처리하는 것입니다. 이 작업을 실행하려면 먼저 Google Developer Console 프로젝트를 설정하고 매니페스트에 몇 가지 매개변수를 추가하여 Firebase 클라우드 메시징(FCM)(이전 명칭: Google 클라우드 메시징(GCM))을 사용해야 합니다.

Firebase Developer Console에서 프로젝트 만들기

Chrome은 FCM을 사용하여 푸시 메시지의 전송 및 전송을 처리합니다. 그러나 FCM API를 사용하려면 Firebase Developer Console에서 프로젝트를 설정해야 합니다.

다음 단계는 FCM을 사용하는 Chrome, Android용 Opera, 삼성 브라우저에만 적용됩니다. 이 문서의 뒷부분에서 다른 브라우저에서 이 기능이 어떻게 작동하는지 살펴보겠습니다.

새 Firebase 개발자 프로젝트 만들기

시작하려면 https://console.firebase.google.com/에서 '새 프로젝트 만들기'를 클릭하여 새 프로젝트를 만들어야 합니다.

새 Firebase 프로젝트 스크린샷

프로젝트 이름을 추가하고 프로젝트를 만들면 프로젝트 대시보드로 이동합니다.

Firebase 프로젝트 홈

이 대시보드에서 왼쪽 상단의 프로젝트 이름 옆에 있는 톱니바퀴 아이콘을 클릭하고 '프로젝트 설정'을 클릭합니다.

Firebase 프로젝트 설정 메뉴

설정 페이지에서 '클라우드 메시징' 탭을 클릭합니다.

Firebase 프로젝트 클라우드 메시징 메뉴

이 페이지에는 나중에 사용할 푸시 메시지용 API 키와 다음 섹션에서 웹 앱 매니페스트에 입력해야 하는 발신자 ID가 포함되어 있습니다.

웹 앱 매니페스트 추가

푸시의 경우 푸시 구독이 성공하려면 gcm_sender_id 필드가 있는 매니페스트 파일을 추가해야 합니다. 이 매개변수는 FCM / GCM을 사용할 수 있도록 Chrome, Android용 Opera, 삼성 브라우저에서만 필요합니다.

gcm_sender_id는 FCM으로 사용자 기기를 구독할 때 이러한 브라우저에서 사용됩니다. 즉, FCM은 사용자의 기기를 식별하고 발신자 ID가 해당 API 키와 일치하는지, 사용자가 서버에서 푸시 메시지를 보내도록 허용했는지 확인할 수 있습니다.

다음은 매우 간단한 매니페스트 파일입니다.

{
    "name": "Push Demo",
    "short_name": "Push Demo",
    "icons": [{
        "src": "images/icon-192x192.png",
        "sizes": "192x192",
        "type": "image/png"
        }],
    "start_url": "/index.html?homescreen=1",
    "display": "standalone",
    "gcm_sender_id": "<Your Sender ID Here>"
}

gcm_sender_id 값을 Firebase 프로젝트의 발신자 ID로 설정해야 합니다.

프로젝트에 매니페스트 파일을 저장한 후 (manifest.json은 좋은 이름임) 다음 태그를 페이지 헤드에 추가하여 HTML에서 참조합니다.

<link rel="manifest" href="/manifest.json">

이러한 매개변수를 사용하여 웹 매니페스트를 추가하지 않으면 사용자의 푸시 메시지를 구독하려고 할 때 "Registration failed - no sender id provided" 또는 "Registration failed - permission denied" 오류와 함께 예외가 발생합니다.

푸시 메시지를 받으려면 구독

매니페스트를 설정했으므로 이제 사이트 자바스크립트로 돌아갈 수 있습니다.

구독하려면 ServiceWorkerRegistration을 통해 액세스하는 PushManager 객체에서 subscribe() 메서드를 호출해야 합니다.

이렇게 하면 푸시 알림을 전송할 수 있는 권한을 출처에 부여하라는 메시지가 사용자에게 표시됩니다. 이 권한이 없으면 구독할 수 없습니다.

subscribe() 메서드에서 반환된 프로미스가 확인되면 엔드포인트가 포함된 PushSubscription 객체가 제공됩니다.

엔드포인트는 나중에 푸시 메시지를 보낼 때 필요하므로 각 사용자의 서버에 저장해야 합니다.

다음 코드는 사용자가 푸시 메시지를 구독하도록 합니다.

function subscribe() {
    // Disable the button so it can't be changed while
    // we process the permission request
    var pushButton = document.querySelector('.js-push-button');
    pushButton.disabled = true;

    navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    serviceWorkerRegistration.pushManager.subscribe()
        .then(function(subscription) {
        // The subscription was successful
        isPushEnabled = true;
        pushButton.textContent = 'Disable Push Messages';
        pushButton.disabled = false;

        // TODO: Send the subscription.endpoint to your server
        // and save it to send a push message at a later date
        return sendSubscriptionToServer(subscription);
        })
        .catch(function(e) {
        if (Notification.permission === 'denied') {
            // The user denied the notification permission which
            // means we failed to subscribe and the user will need
            // to manually change the notification permission to
            // subscribe to push messages
            console.warn('Permission for Notifications was denied');
            pushButton.disabled = true;
        } else {
            // A problem occurred with the subscription; common reasons
            // include network errors, and lacking gcm_sender_id and/or
            // gcm_user_visible_only in the manifest.
            console.error('Unable to subscribe to push.', e);
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
        }
        });
    });
}

이제 웹 앱이 푸시 메시지를 받을 준비가 된 것입니다. 단, 서비스 워커 파일에 푸시 이벤트 리스너를 추가할 때까지 아무 일도 일어나지 않습니다.

서비스 워커 푸시 이벤트 리스너

푸시 메시지가 수신되면 (실제로 푸시 메시지를 보내는 방법은 다음 섹션에서 설명) 서비스 워커에서 푸시 이벤트가 전달되고, 이때 알림을 표시해야 합니다.

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

    var title = 'Yay a message.';
    var body = 'We have received a push message.';
    var icon = '/images/icon-192x192.png';
    var tag = 'simple-push-demo-notification-tag';

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

이 코드는 푸시 이벤트 리스너를 등록하고 사전 정의된 제목, 본문 텍스트, 아이콘, 알림 태그가 있는 알림을 표시합니다. 이 예에서 한 가지 주의할 점은 event.waitUntil() 메서드입니다. 이 메서드는 프로미스를 사용하여 프로미스가 해결될 때까지 이벤트 핸들러의 수명을 연장하거나 서비스 워커를 활성 상태로 유지하는 것으로 간주될 수 있습니다. 이 경우 event.waitUntil에 전달된 프로미스는 showNotification()에서 반환된 프로미스입니다.

알림 태그는 고유 알림의 식별자 역할을 합니다. 동일한 엔드포인트로 푸시 메시지 두 개를 전송했고 사이에 짧은 지연 시간이 있고 동일한 태그가 포함된 알림을 표시하면, 푸시 메시지가 수신될 때 브라우저에 첫 번째 알림이 표시되고 두 번째 알림으로 대체됩니다.

한 번에 여러 알림을 표시하려면 다른 태그를 사용하거나 태그를 아예 사용하지 않습니다. 이 게시물의 뒷부분에서 알림을 표시하는 더 완전한 예를 살펴보겠습니다. 일단은 간단하게 하고 푸시 메시지를 보낼 때 이 알림이 표시되는지 확인해 보겠습니다.

푸시 메시지 보내기

푸시 메시지를 구독했고 서비스 워커가 알림을 표시할 준비가 되었으므로 FCM을 통해 푸시 메시지를 보내야 합니다.

이는 FCM을 사용하는 브라우저에만 적용됩니다.

PushSubscription.endpoint 변수를 서버로 전송하면 FCM의 엔드포인트는 특별합니다. URL 끝에는 registration_id라는 매개변수가 있습니다.

엔드포인트의 예는 다음과 같습니다.

https://fcm.googleapis.com/fcm/send/APA91bHPffi8zclbIBDcToXN_LEpT6iA87pgR-J-MuuVVycM0SmptG-rXdCPKTM5pvKiHk2Ts-ukL1KV8exGOnurOAKdbvH9jcvg8h2gSi-zZJyToiiydjAJW6Fa9mE3_7vsNIgzF28KGspVmLUpMgYLBd1rxaVh-L4NDzD7HyTkhFOfwWiyVdKh__rEt15W9n2o6cZ8nxrP

FCM URL은 다음과 같습니다.

https://fcm.googleapis.com/fcm/send

registration_id는 다음과 같습니다.

APA91bHPffi8zclbIBDcToXN_LEpT6iA87pgR-J-MuuVVycM0SmptG-rXdCPKTM5pvKiHk2Ts-ukL1KV8exGOnurOAKdbvH9jcvg8h2gSi-zZJyToiiydjAJW6Fa9mE3_7vsNIgzF28KGspVmLUpMgYLBd1rxaVh-L4NDzD7HyTkhFOfwWiyVdKh__rEt15W9n2o6cZ8nxrP

이는 FCM을 사용하는 브라우저에만 적용됩니다. 일반 브라우저에서는 단순히 엔드포인트를 가져오고 해당 엔드포인트를 일반적인 방식으로 호출하면 URL과 관계없이 작동합니다.

즉, 서버에서 엔드포인트가 FCM용인지 확인하고, FCM용 엔드포인트인 경우 registration_id를 추출해야 합니다. Python에서 이 작업을 수행하려면 다음과 같이 하면 됩니다.

if endpoint.startswith('https://fcm.googleapis.com/fcm/send'):
    endpointParts = endpoint.split('/')
    registrationId = endpointParts[len(endpointParts) - 1]

    endpoint = 'https://fcm.googleapis.com/fcm/send'

등록 ID를 받으면 FCM API를 호출할 수 있습니다. FCM API의 참조 문서는 여기에서 확인할 수 있습니다.

FCM을 호출할 때 기억해야 할 주요 측면은 다음과 같습니다.

  • API를 호출할 때 값이 key=&lt;YOUR_API_KEY&gt;Authorization 헤더를 설정해야 합니다. 여기서 &lt;YOUR_API_KEY&gt;는 Firebase 프로젝트의 API 키입니다.
    • FCM은 API 키를 사용하여 적절한 발신자 ID를 찾고, 사용자가 프로젝트에 대한 권한을 부여했는지 확인하고, 마지막으로 서버의 IP 주소가 해당 프로젝트에 대해 허용 목록에 있는지 확인합니다.
  • 데이터를 JSON으로 전송하는지 아니면 양식 데이터로 전송하는지에 따라 적절한 Content-Type 헤더의 application/json 또는 application/x-www-form-urlencoded;charset=UTF-8.
  • registration_ids 배열 - 사용자의 엔드포인트에서 추출한 등록 ID입니다.

서버에서 푸시 메시지를 보내는 방법은 문서를 확인하세요. 서비스 워커를 빠르게 확인하려면 cURL을 사용하여 브라우저에 푸시 메시지를 보내면 됩니다.

이 cURL 명령어의 &lt;YOUR_API_KEY&gt;&lt;YOUR_REGISTRATION_ID&gt;를 자체 명령어와 바꾸고 터미널에서 실행하세요.

다음과 같은 멋진 알림이 표시됩니다.

    curl --header "Authorization: key=<YOUR_API_KEY>" --header
    "Content-Type: application/json" https://fcm.googleapis.com/fcm/send -d
    "{\"registration_ids\":[\"<YOUR_REGISTRATION_ID>\"]}"
Android용 Chrome의 푸시 메시지 예

백엔드 로직을 개발할 때 POST 본문의 승인 헤더와 형식은 FCM 엔드포인트에 따라 다르므로 엔드포인트가 FCM용인 경우를 감지하고 조건부로 헤더를 추가하고 POST 본문의 형식을 지정해야 합니다. 다른 브라우저 (및 향후 Chrome 포함)의 경우 웹 푸시 프로토콜을 구현해야 합니다.

현재 Chrome에서 Push API를 구현할 때의 단점은 푸시 메시지로 데이터를 전송할 수 없다는 것입니다. 아니요, 없습니다. 그 이유는 향후 구현에서는 페이로드 데이터를 푸시 메시지 엔드포인트로 전송하기 전에 서버에서 암호화해야 하기 때문입니다. 이렇게 하면 푸시 공급자가 무엇이든 엔드포인트에서 푸시 메시지의 내용을 쉽게 볼 수 없습니다. 이렇게 하면 HTTPS 인증서의 잘못된 유효성 검사, 서버와 푸시 제공업체 간의 중간자 공격과 같은 다른 취약점도 방지할 수 있습니다. 그러나 이 암호화는 아직 지원되지 않으므로 그때까지는 가져오기를 실행하여 알림을 채우는 데 필요한 정보를 가져와야 합니다.

더 완전한 푸시 이벤트 예

지금까지 살펴본 알림은 매우 기본적이며, 샘플에 관해서는 실제 사용 사례를 다루기에는 부족합니다.

현실적으로 대부분의 사용자는 알림을 표시하기 전에 서버에서 정보를 가져오려고 합니다. 이는 알림 제목과 메시지를 특정 내용으로 채우는 데이터이거나 한 단계 더 나아가 일부 페이지나 데이터를 캐시하여 사용자가 알림을 클릭하면 브라우저가 열릴 때 네트워크를 사용할 수 없더라도 모든 것을 즉시 사용할 수 있도록 하는 데이터일 수 있습니다.

다음 코드에서는 API에서 일부 데이터를 가져오고 응답을 객체로 변환한 후 이를 사용하여 알림을 채웁니다.

self.addEventListener('push', function(event) {
    // Since there is no payload data with the first version
    // of push messages, we'll grab some data from
    // an API and use it to populate a notification
    event.waitUntil(
    fetch(SOME_API_ENDPOINT).then(function(response) {
        if (response.status !== 200) {
        // Either show a message to the user explaining the error
        // or enter a generic message and handle the
        // onnotificationclick event to direct the user to a web page
        console.log('Looks like there was a problem. Status Code: ' + response.status);
        throw new Error();
        }

        // Examine the text in the response
        return response.json().then(function(data) {
        if (data.error || !data.notification) {
            console.error('The API returned an error.', data.error);
            throw new Error();
        }

        var title = data.notification.title;
        var message = data.notification.message;
        var icon = data.notification.icon;
        var notificationTag = data.notification.tag;

        return self.registration.showNotification(title, {
            body: message,
            icon: icon,
            tag: notificationTag
        });
        });
    }).catch(function(err) {
        console.error('Unable to retrieve data', err);

        var title = 'An error occurred';
        var message = 'We were unable to get the information for this push message';
        var icon = URL_TO_DEFAULT_ICON;
        var notificationTag = 'notification-error';
        return self.registration.showNotification(title, {
            body: message,
            icon: icon,
            tag: notificationTag
        });
    })
    );
});

event.waitUntil()이 프로미스를 취하여 showNotification()에서 반환된 프로미스를 반환한다는 점을 다시 한번 강조하는 것이 좋습니다. 즉, 비동기 fetch() 호출이 완료될 때까지 이벤트 리스너가 종료되지 않고 알림이 표시됩니다.

오류가 있는 경우에도 알림이 표시됩니다. 그러지 않으면 Chrome에서 자체 일반 알림을 표시하기 때문입니다.

사용자가 알림을 클릭할 때 URL 열기

사용자가 알림을 클릭하면 서비스 워커에 notificationclick 이벤트가 전달됩니다. 핸들러 내에서 탭에 포커스를 맞추거나 특정 URL로 창을 여는 등의 적절한 작업을 할 수 있습니다.

self.addEventListener('notificationclick', function(event) {
    console.log('On notification click: ', event.notification.tag);
    // Android doesn't close the notification when you click on it
    // See: http://crbug.com/463146
    event.notification.close();

    // This looks to see if the current is already open and
    // focuses if it is
    event.waitUntil(
    clients.matchAll({
        type: "window"
    })
    .then(function(clientList) {
        for (var i = 0; i < clientList.length; i++) {
        var client = clientList[i];
        if (client.url == '/' && 'focus' in client)
            return client.focus();
        }
        if (clients.openWindow) {
        return clients.openWindow('/');
        }
    })
    );
});

이 예에서는 기존의 동일 출처 탭에 포커스가 있으면 새 탭을 열어 사이트 출처의 루트로 브라우저를 엽니다.

여기에서 Notification API로 수행할 수 있는 작업에 관한 내용을 다루는 게시물이 있습니다.

사용자 기기 구독 취소

사용자의 기기를 구독했는데 사용자가 푸시 메시지를 받고 있습니다. 그렇다면 어떻게 수신 거부할 수 있을까요?

사용자 기기를 구독 취소하려면 PushSubscription 객체에서 unsubscribe() 메서드를 호출하고 서버에서 엔드포인트를 삭제해야 합니다 (수신되지 않는 푸시 메시지를 전송하지 않도록 하기 위함). 아래 코드는 정확히 다음을 실행합니다.

function unsubscribe() {
    var pushButton = document.querySelector('.js-push-button');
    pushButton.disabled = true;

    navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    // To unsubscribe from push messaging, you need get the
    // subscription object, which you can call unsubscribe() on.
    serviceWorkerRegistration.pushManager.getSubscription().then(
        function(pushSubscription) {
        // Check we have a subscription to unsubscribe
        if (!pushSubscription) {
            // No subscription object, so set the state
            // to allow the user to subscribe to push
            isPushEnabled = false;
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
            return;
        }

        var subscriptionId = pushSubscription.subscriptionId;
        // TODO: Make a request to your server to remove
        // the subscriptionId from your data store so you
        // don't attempt to send them push messages anymore

        // We have a subscription, so call unsubscribe on it
        pushSubscription.unsubscribe().then(function(successful) {
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
            isPushEnabled = false;
        }).catch(function(e) {
            // We failed to unsubscribe, this can lead to
            // an unusual state, so may be best to remove
            // the users data from your data store and
            // inform the user that you have done so

            console.log('Unsubscription error: ', e);
            pushButton.disabled = false;
            pushButton.textContent = 'Enable Push Messages';
        });
        }).catch(function(e) {
        console.error('Error thrown while unsubscribing from push messaging.', e);
        });
    });
}

정기 결제 최신 상태 유지

FCM과 서버 간에 구독이 동기화되지 않을 수 있습니다. FCM 문서에 설명된 대로 서버가 FCM API 전송 POST의 응답 본문을 파싱하여 error:NotRegisteredcanonical_id 결과를 찾아야 합니다.

또한 서비스 워커와 서버 간의 구독이 동기화되지 않을 수 있습니다. 예를 들어 성공적으로 구독/구독 취소한 후에 네트워크 연결이 불안정하면 서버가 업데이트되지 않거나 사용자가 알림 권한을 취소하여 자동 구독 취소가 트리거될 수 있습니다. 이러한 경우에는 serviceWorkerRegistration.pushManager.getSubscription()의 결과를 주기적으로 (예: 페이지 로드 시) 확인하고 서버와 동기화하여 처리하세요. 더 이상 정기 결제가 없고 Notification.permission == 'granted'인 경우에도 자동으로 다시 구독할 수 있습니다.

sendSubscriptionToServer()에서 endpoint를 업데이트할 때 실패한 네트워크 요청을 처리하는 방법을 고려해야 합니다. 한 가지 해결책은 쿠키의 endpoint 상태를 추적하여 서버에 최신 세부정보가 필요한지 여부를 확인하는 것입니다.

위의 모든 단계를 따르면 Chrome 46에서 웹의 푸시 메시지가 완전히 구현됩니다. 푸시 메시지를 트리거하는 표준 API와 같이 작업을 더 쉽게 하는 사양 기능이 아직 있지만, 이번 출시를 통해 지금 바로 웹 앱에 푸시 메시지를 빌드할 수 있습니다.

웹 앱을 디버그하는 방법

푸시 메시지를 구현하는 동안에는 페이지 또는 서비스 워커라는 두 위치 중 하나에 버그가 발생합니다.

페이지의 버그는 DevTools를 사용하여 디버그할 수 있습니다. 서비스 워커 문제를 디버그하는 방법에는 두 가지가 있습니다.

  1. chrome://inspect > 서비스 워커로 이동합니다. 이 뷰는 현재 실행 중인 서비스 워커 이외의 많은 정보를 제공하지 않습니다.
  2. chrome://serviceworker-internals로 이동하면 서비스 워커의 상태를 보고 오류가 있는 경우 이를 확인할 수 있습니다. DevTools에 유사한 기능 세트가 있을 때까지 이 페이지는 임시 페이지입니다.

서비스 워커를 처음 사용하는 분이라면 'DevTools 창 열기 및 서비스 워커 시작 시 디버깅을 위해 JavaScript 실행 일시중지' 체크박스를 선택해 보세요. 이 체크박스는 서비스 워커의 시작 부분에 중단점을 추가하고 실행을 일시중지합니다. 이를 통해 서비스 워커 스크립트를 다시 시작하거나 단계별로 실행하여 문제가 있는지 확인할 수 있습니다.

serviceworker-internals에서 실행 일시중지 체크박스가 있는 위치를 보여주는 스크린샷

FCM과 서비스 워커의 푸시 이벤트 간에 문제가 있는 것으로 보이는 경우 Chrome이 수신했는지 확인할 방법이 없으므로 문제를 디버그하기 위해 취할 수 있는 조치가 없습니다. 핵심은 서버가 API를 호출할 때 FCM의 응답이 성공하는지 확인하는 것입니다. 표시되는 방식은 다음과 같습니다.

{"multicast_id":1234567890,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1234567890"}]}

"success": 1 응답을 확인합니다. 실패가 표시되는 경우 FCM 등록 ID에 문제가 있고 푸시 메시지가 Chrome으로 전송되지 않는 것입니다.

Android용 Chrome에서 서비스 워커 디버깅

현재로서는 Android용 Chrome에서 서비스 워커를 디버깅하는 방법이 명확하지 않습니다. chrome://inspect로 이동하여 기기를 찾아 이름이 'Worker pid:....'이고 서비스 워커의 URL이 포함된 목록 항목을 찾아야 합니다.

Chrome 검사에서 서비스 워커가 있는 위치를 보여주는 스크린샷

푸시 알림 UX

Chrome팀은 푸시 알림 UX 권장사항 문서와 푸시 알림 사용 시 일부 예외적인 사례를 다루는 문서를 작성했습니다.

Chrome 및 오픈 웹에서 푸시 메시지의 미래

이 섹션에서는 이 구현에서 개발자가 알아야 할 Chrome 관련 부분과 다른 브라우저 구현과 어떻게 다른지 자세히 살펴봅니다.

웹 푸시 프로토콜 및 엔드포인트

Push API 표준의 장점은 엔드포인트를 가져와서 서버에 전달하고 웹 푸시 프로토콜을 구현하여 푸시 메시지를 보낼 수 있어야 한다는 것입니다.

웹 푸시 프로토콜은 푸시 제공업체가 구현할 수 있는 새로운 표준으로, 개발자는 푸시 제공자가 누구인지 걱정할 필요가 없습니다. 이렇게 하면 FCM에서와 마찬가지로 API 키를 신청하고 특수 형식의 데이터를 전송할 필요가 없습니다.

Chrome은 Push API를 구현한 첫 번째 브라우저였으며 FCM은 웹 푸시 프로토콜을 지원하지 않습니다. 이 때문에 Chrome에 gcm_sender_id가 필요하고 FCM에 RESTful API를 사용해야 합니다.

Chrome의 최종 목표는 Chrome 및 FCM에서 웹 푸시 프로토콜을 사용하는 방향으로 나아가는 것입니다.

그때까지는 'https://fcm.googleapis.com/fcm/send' 엔드포인트를 감지하여 다른 엔드포인트와 별도로 처리해야 합니다. 즉, 페이로드 데이터의 형식을 특정 방식으로 지정하고 승인 키를 추가해야 합니다.

웹 푸시 프로토콜을 구현하는 방법

Firefox Nightly는 현재 푸시 작업을 진행 중이며 웹 푸시 프로토콜을 구현하는 첫 번째 브라우저가 될 것입니다.

FAQ

사양은 어디에 있나요?

https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ https://w3c.github.io/push-api/ https://notifications.spec.whatwg.org/

웹 인지도에 출처가 여러 개 있거나 웹 및 네이티브 인지도가 모두 있는 경우 중복 알림을 방지할 수 있나요?

현재는 이 문제를 해결할 방법이 없지만 Chromium에서 진행 상황을 확인할 수 있습니다.

이상적인 시나리오는 사용자 기기에 일종의 ID를 두고 서버 측에서 네이티브 앱과 웹 앱 정기 결제 ID를 일치시키고 푸시 메시지를 보낼 ID를 결정하는 것입니다. 화면 크기나 기기 모델을 통해 웹 앱과 네이티브 앱 간에 생성된 키를 공유할 수도 있지만 각 접근 방식에는 장단점이 있습니다.

gcm_sender_id가 필요한 이유는 무엇인가요?

이렇게 해야 Chrome, Opera for Android, 삼성 브라우저에서 FCM (Firebase 클라우드 메시징) API를 사용할 수 있습니다. 목표는 표준이 확정되고 FCM에서 이를 지원할 수 있을 때 웹 푸시 프로토콜을 사용하는 것입니다.

웹 소켓 또는 서버 전송 이벤트 (EventSource)를 사용하지 않는 이유는 무엇인가요?

푸시 메시지를 사용할 때의 장점은 페이지가 닫혀 있어도 서비스 워커의 절전 모드가 해제되어 알림을 표시할 수 있다는 것입니다. 웹 소켓과 EventSource는 페이지나 브라우저가 닫히면 연결이 닫힙니다.

백그라운드 이벤트 전송이 필요하지 않은 경우 어떻게 해야 하나요?

백그라운드 전송이 필요하지 않다면 웹 소켓을 사용하는 것이 좋습니다.

알림을 표시하지 않고 푸시를 사용할 수 있는 경우는 언제인가요 (예: 무음 백그라운드 푸시)?

이 기능의 사용 가능 시점은 아직 정해지지 않았지만 백그라운드 동기화를 구현하려는 인텐트가 있으며, 백그라운드 동기화 구현이 결정되거나 지정되지는 않았지만 백그라운드 동기화를 통한 자동 푸시 사용에 관한 논의가 있습니다.

HTTPS가 필요한 이유는 무엇인가요? 개발 중에 이 문제를 해결하려면 어떻게 해야 하나요?

서비스 워커에서 서비스 워커 스크립트가 의도한 출처에서 비롯되고 중간자 공격에서 비롯된 것이 아님을 확인하려면 서비스 워커에 보안 출처가 필요합니다. 이는 현재 라이브 사이트에서 HTTPS를 사용함을 의미하지만, 개발 중에는 localhost가 작동합니다.

브라우저 지원 형태는 어떻게 되나요?

Chrome은 안정화 버전에서 지원하고 있으며 Mozilla는 Firefox Nightly에서 푸시 작업을 진행하고 있습니다. 자세한 내용은 Push API 구현 버그를 참고하세요. 여기에서 알림 구현을 추적할 수 있습니다.

일정 기간이 지나면 알림을 삭제할 수 있나요?

현재로서는 불가능합니다. 하지만 현재 표시되는 알림 목록을 가져올 수 있도록 지원을 추가할 계획입니다. 알림 생성 후 알림 만료를 설정하는 사용 사례가 있는 경우 이를 Google에 알려주시기 바랍니다. 의견을 추가해 주시면 Chrome팀으로 다시 전달해 드리겠습니다.

일정 기간이 지난 후에 사용자에게 푸시 알림이 전송되는 것을 중지해야 하고 알림이 얼마나 오래 표시되는지에는 상관이 없다면 FCM의 TTL (수명) 매개변수를 사용할 수 있습니다. 여기에서 자세히 알아보세요.

Chrome에서 푸시 메시지의 제한사항은 무엇인가요?

이 게시물에서는 몇 가지 제한사항을 설명합니다.

  • Chrome에서 CCM을 푸시 서비스로 사용하면 여러 가지 독점적인 요구사항이 발생합니다. Google은 이 중 일부를 향후 개선할 수 있는지 알아보기 위해 함께 노력하고 있습니다.
  • 푸시 메시지를 받으면 알림을 표시해야 합니다.
  • 데스크톱용 Chrome에는 Chrome이 실행되고 있지 않으면 푸시 메시지가 수신되지 않는다는 경고가 있습니다. 푸시 메시지가 항상 수신되는 ChromeOS나 Android와는 다릅니다.

Permissions API를 사용하면 안 되나요?

Permission API는 Chrome에서 구현되지만 모든 브라우저에서 사용할 수 있는 것은 아닙니다. 자세히 알아보기

알림을 클릭해도 Chrome에서 이전 탭이 열리지 않는 이유는 무엇인가요?

이 문제는 현재 서비스 워커가 관리하지 않는 페이지에만 영향을 미칩니다. 자세히 알아보기

사용자 기기에서 푸시를 받은 시점까지 알림이 최신 상태가 아니면 어떻게 해야 하나요?

푸시 메시지를 받으면 항상 알림을 표시해야 합니다. 알림을 보내려고 하지만 특정 기간에만 유용한 경우에는 CCM에서 'time_to_live' 매개변수를 사용하면 만료 시간이 지난 후 FCM에서 푸시 메시지를 보내지 않도록 할 수 있습니다.

자세한 내용은 여기를 참고하세요.

푸시 메시지를 10개 보냈는데 기기에서 메시지 수신만 하려면 어떻게 해야 하나요?

FCM에는 동일한 'wrap_key'가 있는 대기 중인 메시지를 새 메시지로 교체하도록 FCM에 지시하는 데 사용할 수 있는 'wrap_key' 매개변수가 있습니다.

자세한 내용은 여기를 참고하세요.