알림 API 사용하기

주의: 이 기능은 Web Worker에서 사용할 수 있습니다.

Secure context
This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.

웹 페이지나 앱에서 알림(Notifications) API를 사용하면 페이지 외부에 표시되는 알림을 보낼 수 있습니다. 이것은 시스템 레벨에서 처리되는 것으로 애플리케이션이 유휴 상태거나 백그라운드에 있더라도 웹 앱이 사용자에게 정보를 보낼 수 있습니다. 이 글에서는 여러분의 앱에서 이 API를 사용하기 위한 기초를 알아봅니다.

일반적으로 시스템 알림은 운영 체계의 표준 알림 메커니즘을 말합니다. 예를 들어 일반적인 데스크톱 시스템이나 모바일 장치의 브로드캐스트 알림을 생각해봅시다.

물론 시스템 알림 시스템은 플랫폼 및 브라우저에 따라 다양하지만 괜찮습니다. 알림 API는 범용적으로 작성돼서 대부분의 시스템 알림 시스템과 호환됩니다.

예시

웹 알림의 대표적인 사용 사례는 웹 기반 메일이나 IRC 애플리케이션입니다. 새 메시지가 도착하면 사용자가 다른 애플리케이션으로 다른 일을 하더라도 사용자에게 알릴 필요가 있습니다. 요즘은 Slack 등 이러한 사례를 많이 찾아볼 수 있습니다.

우리는 웹 알림을 사용하는 방법을 좀더 잘 알 수 있도록 실제적인 예시 — 할 일 목록 앱 —를 작성했습니다. 데이터는 로컬에서 IndexedDB로 저장하고 사용자 알림은 할 일 기한이 됐을 때 시스템 알림을 사용합니다. 할 일 목록 코드를 다운로드하거나, 앱의 라이브 실행을 보세요.

권한 요청하기

앱이 알림을 보내려면 먼저 사용자가 애플리케이션에 해당 권한을 허용해줘야 합니다. 이는 API가 웹페이지 외부와 상호작용할 때 통상적인  요구 사항입니다. 최소 한번은 사용자가 해당 애플리케이션이 알림을 표시할 수 있는 권한을 허용해줄 필요가 있으며 이로써 사용자는 어떤 앱/사이트가 알림을 보일 수 있는지 제어할 수 있습니다.

과거에 푸시 알림에 대한 악용 때문에 웹 브라우저와 개발자는 그런 문제를 완화할 수 있는 전략을 구현하게 되었습니다. 알림을 발생시키려면 사용자 제스처(예: 단추 클릭)에 대한 응답으로만 가능합니다. 이것은 모범적인 방식일 뿐 아니라 — 사용자에게 미동의 알림으로 스팸을 보내면 안됩니다 — 실제로도 전향적인 브라우저는 사용자 제스처에 대한 응답으로 촉발되지 않은 알림은 명시적으로 불허합니다. 파이어폭스는 이미 72 버전부터 이렇게 하고 있으며 사파리도 하고 있습니다.

또한 크롬과 파이어폭스에서는 사이트가 보안 콘텍스트(즉, HTTPS)가 아니면 알림을 아예 요청할 수 없으며 크로스 오리진 <iframe>으로부터의 알림 권한은 요청할 수 없게 되었습니다. 

현재 권한 상태 확인하기

권한을 이미 가지고 있는지 확인하려면 Notification.permission 읽기 전용 속성의 값을 확인하면 됩니다. 다음 세 가지 값이 있을 수 있습니다.

default
사용자에게 아직 권한을 요구하지 않았으며 따라서 알림을 표시하지 않습니다.
granted
사용자에게 알림 표시 권한을 요구했으며 사용자는 권한을 허용했습니다.
denied
사용자가 명시적으로 알림 표시 권한을 거부했습니다.

권한 획득하기

아직 알림 표시 권한이 허용되지 않았다면 애플리케이션은 Notification.requestPermission() 메서드를 사용하여 사용자에게 권한을 요청할 필요가 있습니다. 간단하게는 아래와 같이 넣습니다.

Notification.requestPermission().then(function(result) {
  console.log(result);
});

여기서는 프로미스 방식의 메서드 버전을 사용합니다. 과거 버전을 지원하려면 아래와 같이 과거의 콜백 버전을 사용해야 할 수 있습니다.

Notification.requestPermission();

콜백 버전은 콜백 함수를 옵셔널하게 받을 수 있으며 사용자가 표시 권한 요청에 응답한 후에 호출됩니다.

예시

우리가 만드는 할 일 데모에서는 "알림 허용" 단추를 둬서 누르면 앱의 알림 권한을 요청합니다.

<button id="enable">알림 허용</button>

누르면 다음 askNotificationPermission() 함수를 호출합니다.

function askNotificationPermission() {
  // 권한을 실제로 요구하는 함수
  function handlePermission(permission) {
    // 사용자의 응답에 관계 없이 크롬이 정보를 저장할 수 있도록 함
    if(!('permission' in Notification)) {
      Notification.permission = permission;
    }

    // 사용자 응답에 따라 단추를 보이거나 숨기도록 설정
    if(Notification.permission === 'denied' || Notification.permission === 'default') {
      notificationBtn.style.display = 'block';
    } else {
      notificationBtn.style.display = 'none';
    }
  }

  // 브라우저가 알림을 지원하는지 확인
  if (!('Notification' in window)) {
    console.log("이 브라우저는 알림을 지원하지 않습니다.");
  } else {
    if(checkNotificationPromise()) {
      Notification.requestPermission()
      .then((permission) => {
        handlePermission(permission);
      })
    } else {
      Notification.requestPermission(function(permission) {
        handlePermission(permission);
      });
    }
  }
}

두 번째 메인 블록을 먼저 보면 알림이 지원되는지 확인하는 것을 알 수 있습니다. 지원하는 경우 그에 따라 Notification.requestPermission()의 프로미스 기반 버전이 지원되는지 보는 확인을 실행합니다. 맞다면 프로미스 기반 버전을 실행하고(사파리 외에는 전부 지원됨) 아니라면 과거의 콜백 기반 버전을 실행합니다(사파리에서 지원).

코드 중복을 피하기 위해 뒷 처리 수행 코드를 handlePermission() 함수에 넣었는데 이 함수가 코드에서 첫 번째 메인 블록입니다. 그 안에서는 Notification.permission 값을 명시적으로 설정하고(크롬의 일부 과거 버전에서는 이게 자동으로 안됩니다) 사용자가 권한 대화창에서 선택한 결과에 따라 단추를 보이거나 숨깁니다. 권한이 이미 허용됐는지 보여주려는 것은 아니고 사용자가 권한을 거부한 경우 나중에 다시 선택할 수 있도록 해주는 것입니다.

참고: 크롬 37 버전 전에는 load 이벤트 핸들러에서 Notification.requestPermission()을 호출할 수 없었습니다(이슈 274284 참고).

requestPermission() 프로미스 기능 알아내기

위에서 우리는 브라우저가 Notification.requestPermission()의 프로미스 버전을 지원하는지 확인해야 한다고 했습니다. 아래와 같이 했습니다.

function checkNotificationPromise() {
    try {
      Notification.requestPermission().then();
    } catch(e) {
      return false;
    }

    return true;
  }

기본적으로 requestPermission()에 .then() 메서드가 있는지 알아보는 것입니다. 맞다면 계속 진행하고 true를 반환합니다. 실패라면 catch() {} 블록에서 false를 반환합니다.

알림 만들기 

알림 만들기는 쉬워서 Notification 생성자만 사용하면 됩니다. 이 생성자는 알림에 표시할 제목과 icon이나 텍스트 body 같은 알림 조작 옵션 몇 가지를 받도록 돼 있습니다.

예를 들어 할일 목록 예시에서 아래 코드로 필요시 알림을 만듭니다(createNotification() 함수에서 찾을 수 있음).

var img = '/to-do-notifications/img/icon-128.png';
var text = '아! "' + title + '" 작업 기한이 만료됐습니다.';
var notification = new Notification('할 일 목록', { body: text, icon: img });

알림 닫기

파이어폭스와 사파리는 알림을 자동으로 금방(약 4초) 닫습니다. 이것은 운영 체계 수준에서도 발생합니다. 그런데 크롬 같은 다른 브라우저는 그렇지 않습니다. 모든 브라우저에서 알림이 닫히게 하려면 setTimeout() 함수에서 Notification.close 함수를 호출하여 알림을 4초 후에 닫으면 됩니다. bind()를 사용하여 close() 호출이 알림에 연동되게 하는 것도 해줘야 합니다.

setTimeout(notification.close.bind(notification), 4000);

참고: "close" 이벤트를 받았을 때 알림을 닫은 것이 사용자인지는 보장할 수 없습니다. 이것은 규격과도 일치합니다. 규격에서는 "알림이 닫힐 때 그것이 기반 알림 플랫폼에 의한 것이든지 사용자에 의한 것이든지 닫기 절차가 실행돼야 한다."고 기술하고 있습니다.

알림 이벤트

Notification 인스턴스에 촉발되는 이벤트는 다음 네 가지입니다.

click
사용자가 알림을 클릭하면 촉발됩니다.
close
알림이 닫힌 후 촉발됩니다.
error
알림에 문제가 있을 경우 촉발되며 대개 어떤 이유에 의해 알림을 표시할 수 없는 경우입니다.
show
알림이 사용자에게 표시되면 촉발됩니다.

이 이벤트들은 onclickoncloseonerror, onshow 핸들러로 추적할 수 있습니다. Notification이 EventTarget을 상속하기 때문에 addEventListener() 메서드를 사용할 수 있습니다.

기존 알림 대체하기

사용자가 잠깐 사이에 알림을 많이 받는 것은 바람직하지 않습니다. 예를 들어 메신저 애플리케이션이 모든 수신 메시지를 사용자에게 알리는데 그게 아주 많다면요? 사용자가 알림 때문에 대량 스팸을 받지 않도록 알림 대기열(큐)을 수정해서 걸려 있는 알림 하나나 여럿을 새로운 알림 하나로 대체할 수 있습니다.

이를 위해 새 알림에 태그를 붙일 수 있습니다. 알림에 이미 같은 태그가 있고 표시되지 않았다면 새 알림으로 이전 알림을 대체하는 것입니다. 같은 태그의 알림이 이미 표시됐다면 이전 알림을 닫고 새 알림을 표시합니다.

태그 예시

다음과 같은 간단한 HTML을 봅시다.

<button>알림 실행!</button>

다수의 알림을 아래 방법으로 처리할 수 있습니다.

window.addEventListener('load', function () {
  // 처음에는 알림 권한이 있는지 확인함
  // 없으면 권한 요구
  if (Notification && Notification.permission !== "granted") {
    Notification.requestPermission(function (status) {
      if (Notification.permission !== status) {
        Notification.permission = status;
      }
    });
  }

  var button = document.getElementsByTagName('button')[0];

  button.addEventListener('click', function () {
    // 사용자가 알림을 받는 데 동의한 경우
    // 알림 10개를 보내본다
    if (Notification && Notification.permission === "granted") {
      var i = 0;
      // 어떤 브라우저(파이어폭스 등)는 일정 시간 동안 알림이 너무 많은 경우 차단하기 때문에 인터벌 사용.
      var interval = window.setInterval(function () {
        // 태그 덕분에 "안녕! 9" 알림만 보여야 함
        var n = new Notification("안녕! " + i, {tag: '알림너무많음'});
        if (i++ == 9) {
          window.clearInterval(interval);
        }
      }, 200);
    }

    // 사용자가 알림을 받을지 말지 답하지 않은 경우
    // 참고: 크롬 때문에 권한 속성이 설정됐는지 알 수 없으므로
    // "기본" 값을 확인하는 것은 안전하지 않음
    else if (Notification && Notification.permission !== "denied") {
      Notification.requestPermission(function (status) {
        // 사용자가 ok한 경우
        if (status === "granted") {
          var i = 0;
          // 어떤 브라우저(파이어폭스 등)는 일정 시간 동안 알림이 너무 많은 경우 차단하기 때문에 인터벌 사용.
          var interval = window.setInterval(function () {
            // 태그 덕분에 "안녕! 9" 알림만 보여야 함
            var n = new Notification("안녕! " + i, {tag: '알림너무많음'});
            if (i++ == 9) {
              window.clearInterval(interval);
            }
          }, 200);
        }

        // 그 외의 경우 일반적인 모달 alert로 폴백
        else {
          alert("안녕!");
        }
      });
    }

    // 사용자가 알림을 거부한 경우
    else {
      // 일반적인 모달 alert로 폴백
      alert("안녕!");
    }
  });
});

라이브 결과는 아래에서 보세요.

규격

규격 상태 비고
Notifications API Living Standard 현행 표준

브라우저 호환성

Update compatibility data on GitHub
DesktopMobile
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung Internet
NotificationChrome Full support 22
Notes
Full support 22
Notes
Notes Before Chrome 22, the support for notification followed an old prefixed version of the specification and used the navigator.webkitNotifications object to instantiate a new notification. Before Chrome 32, Notification.permission was not supported. Before Chrome 42, service worker additions were not supported. Starting in Chrome 49, notifications do not work in incognito mode.
Full support 5
Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge Full support 14Firefox Full support 22
Full support 22
Full support 4
Prefixed
Prefixed Implemented with the vendor prefix: moz
IE No support NoOpera Full support 25Safari Full support 6WebView Android No support NoChrome Android Full support YesFirefox Android Full support 22
Full support 22
Full support 4
Prefixed
Prefixed Implemented with the vendor prefix: webkit
Opera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
Notification() constructorChrome Full support 22
Full support 22
Full support 5
Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge Full support ≤18Firefox Full support 22
Full support 22
Full support 4
Prefixed
Prefixed Implemented with the vendor prefix: moz
IE No support NoOpera Full support 25Safari Full support 6WebView Android No support NoChrome Android Full support YesFirefox Android Full support 22
Full support 22
Full support 4
Prefixed
Prefixed Implemented with the vendor prefix: moz
Opera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
actionsChrome Full support 53Edge Full support 18Firefox No support NoIE No support NoOpera Full support 39Safari ? WebView Android No support NoChrome Android Full support 53Firefox Android No support NoOpera Android Full support 41Safari iOS No support NoSamsung Internet Android Full support 6.0
badgeChrome Full support 53Edge Full support 18Firefox No support NoIE No support NoOpera Full support 39Safari ? WebView Android No support NoChrome Android Full support 53Firefox Android No support NoOpera Android Full support 41Safari iOS No support NoSamsung Internet Android Full support 6.0
bodyChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
closeChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
dataChrome Full support YesEdge Full support 16Firefox Full support YesIE No support NoOpera Full support YesSafari ? WebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
dirChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
iconChrome Full support 22
Full support 22
Full support 5
Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge Full support 14Firefox Full support 22
Full support 22
Full support 4
Prefixed
Prefixed Implemented with the vendor prefix: moz
IE No support NoOpera Full support 25Safari No support NoWebView Android No support NoChrome Android Full support YesFirefox Android Full support 22
Full support 22
Full support 4
Prefixed
Prefixed Implemented with the vendor prefix: moz
Opera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
imageChrome Full support 53Edge Full support 18Firefox No support NoIE No support NoOpera Full support 40Safari ? WebView Android No support NoChrome Android Full support 53Firefox Android No support NoOpera Android Full support 41Safari iOS No support NoSamsung Internet Android Full support 6.0
langChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
maxActionsChrome Full support YesEdge Full support 18Firefox No support NoIE No support NoOpera Full support YesSafari ? WebView Android No support NoChrome Android Full support YesFirefox Android No support NoOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
onclickChrome Full support YesEdge Full support 14Firefox Full support 22IE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android No support NoOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
oncloseChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
onerrorChrome Full support YesEdge Full support 14Firefox No support NoIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android No support NoOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
onshowChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
permissionChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
renotifyChrome Full support 50Edge Full support 79Firefox No support NoIE No support NoOpera Full support 37Safari No support NoWebView Android No support NoChrome Android Full support 50Firefox Android No support NoOpera Android Full support 37Safari iOS No support NoSamsung Internet Android Full support 5.0
requestPermissionChrome Full support 46Edge Full support 14Firefox Full support 47
Notes
Full support 47
Notes
Notes From Firefox 70 onwards, cannot be called from a cross-origin IFrame.
Notes From Firefox 72 onwards, can only be called in response to a user gesture such as a click event.
IE No support NoOpera Full support 40Safari Full support YesWebView Android No support NoChrome Android Full support 46Firefox Android Full support YesOpera Android Full support 41Safari iOS No support NoSamsung Internet Android Full support 5.0
requireInteractionChrome Full support YesEdge Full support 17Firefox No support NoIE No support NoOpera Full support YesSafari ? WebView Android No support NoChrome Android Full support YesFirefox Android No support NoOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
Secure context requiredChrome Full support 62Edge Full support ≤79Firefox Full support 67IE No support NoOpera Full support 49Safari ? WebView Android No support NoChrome Android Full support 62Firefox Android Full support 67Opera Android Full support 46Safari iOS No support NoSamsung Internet Android Full support 8.0
silentChrome Full support 43Edge Full support 17Firefox No support NoIE No support NoOpera Full support 30Safari No support NoWebView Android No support NoChrome Android Full support 43Firefox Android No support NoOpera Android Full support 30Safari iOS No support NoSamsung Internet Android Full support 4.0
tagChrome Full support YesEdge Full support 14Firefox Full support YesIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android Full support YesOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
timestampChrome Full support YesEdge Full support 17Firefox No support NoIE No support NoOpera Full support YesSafari ? WebView Android No support NoChrome Android Full support YesFirefox Android No support NoOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
titleChrome Full support YesEdge Full support 14Firefox No support NoIE No support NoOpera Full support YesSafari Full support YesWebView Android No support NoChrome Android Full support YesFirefox Android No support NoOpera Android Full support YesSafari iOS No support NoSamsung Internet Android Full support Yes
vibrateChrome No support NoEdge No support NoFirefox No support NoIE No support NoOpera No support NoSafari ? WebView Android No support NoChrome Android Full support 53
Notes
Full support 53
Notes
Notes Does not work on Android O or later regardless of Chrome version.
Firefox Android No support NoOpera Android Full support 41
Notes
Full support 41
Notes
Notes Does not work on Android O or later regardless of Chrome version.
Safari iOS No support NoSamsung Internet Android Full support 6.0
Notes
Full support 6.0
Notes
Notes Does not work on Android O or later regardless of Chrome version.
Available in workersChrome Full support 45Edge Full support ≤18Firefox Full support 41IE No support NoOpera Full support 32Safari ? WebView Android No support NoChrome Android Full support 45Firefox Android Full support 41Opera Android Full support 32Safari iOS No support NoSamsung Internet Android Full support 5.0

Legend

Full support  
Full support
No support  
No support
Compatibility unknown  
Compatibility unknown
See implementation notes.
See implementation notes.
Requires a vendor prefix or different name for use.
Requires a vendor prefix or different name for use.

참고