오픈 웹의 푸시 알림

개발자들에게 휴대기기 기능 중 웹에서 사용할 수 없는 것이 무엇인지 묻는다면 언제나 푸시 알림이라는 답이 우선적으로 나옵니다.

푸시 알림을 사용하면 사용자가 자신이 좋아하는 사이트에서 시기 적절하게 업데이트를 받도록 옵트인할 수 있고 개발자는 사용자를 효과적으로 다시 참여시킬 수 있습니다.

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가 여러 개 있습니다. 모든 API가 지원되는 경우 UI를 사용 설정하고 푸시 메시지가 구독되어 있는지 여부를 나타내는 초기 상태를 설정할 수 있습니다.

이러한 검사의 대부분은 UI가 사용 중지되므로 초기 상태를 사용 중지하도록 설정해야 합니다. 이렇게 하면 페이지의 JavaScript에 문제가 발생할 경우(예: JS 파일을 다운로드할 수 없거나 사용자가 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를 사용할 수 있는지 확인합니다. 이 ID가 없으면 푸시 메시지가 수신될 때 서비스 워커의 알림을 표시할 수 없습니다.
  • 현재 Notification.permission"denied"이 아닌지 확인합니다. 권한이 거부되면 사용자가 브라우저에서 직접 권한을 변경할 때까지 알림을 표시할 수 없습니다.
  • 푸시 메시지가 지원되는지 확인하려면 PushManager가 창 객체에서 사용 가능한지 확인합니다.
  • 마지막으로 pushManager.getSubscription()를 사용하여 이미 정기 결제 중인지 확인했습니다. 구독이 있는 경우 올바른 정보가 있는지 확인하기 위해 서버에 구독 세부정보를 전송하고 푸시 메시지가 이미 사용 설정되었는지 여부를 나타내도록 UI를 설정합니다. 이 문서의 뒷부분에서 구독 객체에 있는 세부정보를 살펴봅니다.

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

다음 단계는 사용자가 푸시 메시지를 사용 설정하려는 경우를 처리하는 것입니다. 하지만 이를 수행하기 전에 Google Developers Console 프로젝트를 설정하고 이전의 Google Cloud Messaging (GCM)으로 알려진 Firebase Cloud Messaging (FCM)을 사용하기 위해 매니페스트에 몇 가지 매개변수를 추가해야 합니다.

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

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

다음 단계는 FCM을 사용하는 Chrome, Android용 Opera, Samsung 브라우저에만 적용됩니다. 다른 브라우저에서 작동하는 방식은 이 도움말의 뒷부분에서 설명합니다.

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

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

새 Firebase 프로젝트 스크린샷

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

Firebase 프로젝트 홈

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

Firebase 프로젝트 설정 메뉴

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

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

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

웹 앱 매니페스트 추가

푸시의 경우 푸시 구독이 성공적으로 이루어지도록 gcm_sender_id 필드가 있는 매니페스트 파일을 추가해야 합니다. 이 매개변수는 Chrome, Android용 Opera, Samsung 브라우저에서 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() 메서드에서 반환된 promise가 확인되면 endpoint가 포함된 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()에서 반환된 Promise입니다.

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

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

푸시 메시지 보내기

푸시 메시지를 구독했고 서비스 워커가 알림을 표시할 준비가 되었으므로 이제 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;승인 헤더를 설정해야 합니다. 여기서 &lt;YOUR_API_KEY&gt;는 Firebase 프로젝트의 API 키입니다.
    • API 키는 FCM에서 적절한 발신자 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 > Service workers로 이동합니다. 이 뷰는 현재 실행 중인 서비스 워커 외에는 많은 정보를 제공하지 않습니다.
  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로 이동하여 기기를 찾은 다음 서비스 워커의 URL이 포함된 'Worker pid:....'라는 이름의 목록 항목을 찾아야 합니다.

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

푸시 알림 UX

Chrome팀은 푸시 알림 UX에 관한 권장사항 문서와 푸시 알림을 사용할 때 발생하는 일부 특이 사례를 다루는 문서를 작성하고 있습니다.

Chrome 및 개방형 웹의 푸시 메시지 미래

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

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

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

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

Chrome은 푸시 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, Android용 Opera, Samsung 브라우저에서 Firebase 클라우드 메시징 (FCM) API를 사용할 수 있도록 하려면 이 작업이 필요합니다. 목표는 표준이 완성되고 FCM에서 이를 지원할 수 있을 때 웹 푸시 프로토콜을 사용하는 것입니다.

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

푸시 메시지를 사용하면 페이지가 닫혀 있어도 서비스 워커가 깨어나 알림을 표시할 수 있다는 이점이 있습니다. WebSockets 및 EventSource의 연결은 페이지 또는 브라우저가 닫힐 때 닫힙니다.

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

백그라운드 전송이 필요하지 않은 경우 웹 소켓이 좋은 옵션입니다.

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

아직 이 기능을 사용할 수 있는 시기는 정해지지 않았지만 백그라운드 동기화를 구현하려는 의도가 있으며 아직 결정 또는 사양화되지는 않았지만 백그라운드 동기화를 통해 무음 푸시를 사용 설정하는 것에 관한 논의가 있습니다.

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

서비스 워커는 서비스 워커 스크립트가 의도한 출처에서 가져온 것이고 중간자 공격에서 비롯된 것이 아님을 보장하기 위해 안전한 출처가 필요합니다. 현재로서는 로컬 사이트에서 HTTPS를 사용해야 하지만 개발 중에는 localhost가 작동합니다.

브라우저 지원은 어떻게 표시되나요?

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

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

현재는 불가능하지만 현재 표시되는 알림 목록을 가져오는 지원을 추가할 계획입니다. 알림이 생성된 후 알림의 만료를 설정해야 하는 사용 사례가 있는 경우 알려주시면 Chrome팀에 전달하겠습니다. 댓글을 추가해 주세요.

특정 기간이 지난 후에만 사용자에게 푸시 알림이 전송되지 않도록 멈추고 알림이 표시되는 시간은 중요하지 않다면 FCM의 수명 (ttl) 매개변수를 사용하면 됩니다. 여기에서 자세히 알아보세요.

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

이 게시물에는 몇 가지 제한사항이 설명되어 있습니다.

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

Permissions API를 사용해야 하지 않나요?

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

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

이 문제는 현재 서비스 워커에 의해 제어되지 않는 페이지에만 영향을 미칩니다. 자세히 알아보기

사용자 기기에서 푸시를 수신할 때 알림이 오래된 경우 어떻게 되나요?

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

자세한 내용은 여기에서 확인하세요.

푸시 메시지를 10개 전송했지만 기기에서 하나만 수신하도록 하려면 어떻게 해야 하나요?

FCM에는 'collapse_key' 파라미터가 있습니다. 이 파라미터를 사용하여 FCM에 동일한 'collapse_key'가 있는 대기 중인 메시지를 새 메시지로 대체하도록 지시할 수 있습니다.

자세한 내용은 여기에서 확인하세요.