오픈 웹의 푸시 알림

개발자에게 웹에서 누락된 휴대기기 기능이 무엇인지 물어보면 푸시 알림이 항상 상위에 옵니다.

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

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 파일을 다운로드할 수 없거나 사용자가 JavaScript를 사용 중지하는 등 페이지의 JavaScript에 문제가 있는 경우 혼란을 방지할 수 있습니다.

<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 개발자 콘솔에서 프로젝트 만들기

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 필드가 있는 매니페스트 파일을 추가하여 푸시 구독이 성공하도록 해야 합니다. 이 매개변수는 Chrome, Android용 Opera, 삼성 브라우저에서만 FCM / GCM을 사용할 수 있도록 필요합니다.

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" 오류와 함께 예외가 발생합니다.

푸시 메시지 구독

이제 매니페스트를 설정했으므로 사이트의 JavaScript로 돌아갈 수 있습니다.

구독하려면 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용인지 확인하고 있으면 register_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으로 전송하는지 양식 데이터로 보내는지에 따라 application/json 또는 application/x-www-form-urlencoded;charset=UTF-8의 적절한 Content-Type 헤더입니다.
  • 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에 유사한 기능 세트가 있을 때까지 임시입니다.

서비스 워커를 처음 사용하는 모든 사람에게 제공할 수 있는 최고의 팁 중 하나는 '디버깅을 위해 서비스 워커 시작 시 JavaScript 실행을 일시중지하고 DevTools 창을 일시중지합니다'라는 체크박스를 사용하는 것입니다. 이 체크박스를 선택하면 서비스 워커 시작 부분에 중단점이 추가되고 실행이 일시중지되며, 이를 통해 서비스 워커 스크립트를 다시 시작하거나 진행하면서 문제가 발생하는지 확인할 수 있습니다.

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로 이동하여 기기를 찾은 다음 서비스 워커의 URL이 포함된 'Worker pid:....'라는 목록 항목을 찾습니다.

Chrome Inspector에서 서비스 워커의 위치를 보여주는 스크린샷

푸시 알림 UX

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

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

이 섹션에서는 이 구현에서 개발자가 알아야 하는 일부 Chrome 관련 부분과 다른 브라우저 구현과의 차이점에 대해 자세히 설명합니다.

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

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

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

Chrome은 Push API를 구현한 최초의 브라우저였으며 FCM은 웹 푸시 프로토콜을 지원하지 않습니다. 이러한 이유로 Chrome에는 gcm_sender_id가 필요하며 FCM용 안정 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, Android용 Opera, 삼성 브라우저에서 Firebase 클라우드 메시징 (FCM) API를 사용할 수 있습니다. 표준이 확정되고 FCM에서 지원할 수 있을 때 웹 푸시 프로토콜을 사용하는 것이 목표입니다.

Web Sockets 또는 Server-Sent Events (EventSource)를 사용하지 않는 이유는 무엇인가요?

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

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

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

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

이 기능이 언제 제공될지는 아직 정해지지 않았지만, 백그라운드 동기화를 구현하는 인텐트가 있으며, 결정되거나 사양되지는 않지만 백그라운드 동기화로 자동 푸시를 사용 설정하는 방법에 관한 설명이 있습니다.

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

서비스 워커는 서비스 워커 스크립트가 의도된 출처에서 비롯되고 중간자 공격으로부터 발생하지 않도록 하기 위해 안전한 출처가 필요합니다. 현재 이는 실제 사이트에서 HTTPS를 사용하는 것을 의미하지만 개발 중에는 localhost를 사용할 수 있습니다.

브라우저 지원은 어떻게 이루어지나요?

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

특정 기간이 지난 후 알림을 삭제할 수 있나요?

현재로서는 불가능합니다. 하지만 현재 표시되는 알림 목록을 가져오는 지원을 추가할 계획입니다. 알림 생성 후 알림 만료 시간을 설정하는 사용 사례가 있다면 그 내용을 알려 주시기 바랍니다. 의견을 추가하면 Chrome팀에 다시 전달하겠습니다.

일정 기간 이후에만 사용자에게 푸시 알림이 전송되는 것을 중단해야 하고 알림이 얼마 동안 표시되는지 상관없는 경우 FCM의 TTL (수명) 매개변수를 사용하면 됩니다. 여기에서 자세히 알아보세요.

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

이 게시물에 설명된 몇 가지 제한사항은 다음과 같습니다.

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

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

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

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

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

사용자 기기에서 푸시를 수신한 시점에 알림이 최신 상태가 아닌 경우 어떻게 하나요?

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

자세한 내용은 여기에서 확인할 수 있습니다.

푸시 메시지를 10개 보냈는데 기기가 하나만 받도록 하면 어떻게 되나요?

FCM에는 '축소_키' 매개변수가 있어서 동일한 '축소_키'를 가진 대기 중인 메시지를 새 메시지로 교체하도록 FCM에 지시할 수 있습니다.

자세한 내용은 여기에서 확인할 수 있습니다.