Search
Duplicate

WEB PUSH

수업
Network Study
주제
5 more properties

웹 푸시

Service-worker-notification
AkileshRao

웹 푸시 동작

웹 푸시가 전반적으로 어떻게 동작하는지 메커니즘에 대해 살펴본다.

풀 & 푸시

먼저 풀과 푸시의 차이를 아래 그림을 보며 살펴봅시다.
pull: 요청-응답
push: 서버에서 일방적인 응답

웹 푸시의 구성요소

브라우저 - 푸시 서비스 - 서버
서비스에서 어떤 사용자에게, 어떤 메시지를 보낼지에 대한 내용이 담긴 메시지 데이터를 푸시 서비스로 전달하면, 푸시 서비스에서 사용자 정보를 식별한 후 목적지로 전달한다.
웹 푸시이기 떄문에 여기서 이야기하는 목적지는 브라우저가 된다.
위의 흐름도를 알기 위해서는 웹 푸시 프로토콜에 대해 알아야한다.

웹 푸시 프로토콜

웹 푸시 프로토콜은 푸시 알림을 수신하는 브라우저와 발송하는 서버가 푸시서비스와 상호작용하기 위해 놓인 규약입니다.
클라이언트: 정보등록
서버: 메시지 발송
푸시 서비스 구독
1.
푸시를 수신하게될 클라이언트(브라우저)에서는 푸시 서비스로 내가 누군지 알린다. (구독)
2.
성공적으로 구독된 경우 푸시 서비스에서는 구독 정보를 브라우저에게 돌려줍니다.
3.
구독 정보에는 브라우저를 식별하는 정보가 담겨있으며, 서버에서 특정 브라우저(사용자)에게 푸시 메시지를 보낼 때 사용합니다.

안전한 메시지를 위한 VAPID(Voluntary Application Server Identification)

서버에서 푸시 메시지를 전달 할 때 그냥 전달하지 않는다. 푸시 메시지는 민감하기 때문에 안전하게 암호화되어야 하며 메시지가 어떤 서버에서 발송되었는지 알 수 있어야 한다.
웹 푸시에서는 어떤 서버에서 메시지를 발송했는지 식별하기 위해 VPAID 인증 방식을 사용합니다.

구현방법

1.
애플리케이션 서버는 공개/비공개 키 쌍을 생성합니다.
2.
사용자가 푸시 알람을 구독할 때, 애플리케이션은 공개 키를 푸시 서비스에 전달합니다. 이 공개키는 서버의 신원을 확인하는데 사용됩니다.
3.
JWT 생성, 애플리케이션 서버는 VAPID 클레임을 포함한 JWT를 생성합니다. 이 토큰은 비공개 키로 서명되며, 애플리케이션 서버의 신원 정보와 메시지 전송 요청 기간등을 포함합니다.
4.
애플리케이션 서버가 푸시 서비스에 메시지를 보낼 때 생성된 JWT를 Autorization 헤더에 포함하여 전송하며, 푸시서비스는 이 토큰을 검증하여 요청이 유효한 서버로부터 왔는지 확인합니다.
VAPID 인증과 푸시 알림
4.
서버에서 푸시 서비스로 메시지를 전달할 때 VAPID 명세에 따른 정보가 담긴 JWT를 함께 전달하게 되는데, 이때 토근을 VPAID의 비공개 키로 암호화한다.
5.
서명된 토큰은 푸시 서비스에서 공개 키로 복호화 하여 유효성을 검증하게 된다.
이러한 절차를 통해 푸시 서비스에서 어떤 서버로부터 수신한 메시지인지, 유효한 메시지인지 검증할 수 있게 된다.

서비스 워커(Service Worker)

서비스 워커는 웹 애플리케이션, 특히 프로그레시브 웹 앱(PWA)에서 중요한 역할을 하는 웹 API입니다.
서비스 워커는 백그라운드에서 실행되며, 웹 애플리케이션과 브라우저 사이의 프록시 서버 역할을 하며, 이를 통해서 오프라인 경험을 개선하고, 네트워크 요청을 캐싱하며 데이터를 가전에 가져와 사용자 경험을 향상시키는 다양한 기능을 수행합니다.
서비스 워커는 먼저 웹 애플리케이션에 등록되어야 하고, 등록과정에서 서비스 워커 파일이 다운로드되고 설치합니다. 설치과정에서 캐시에 파일을 저장하는 등의 초기 설정 작업을 수행할 수 있습니다.
설치이후, 서비스 워커는 활성화 단계를 거치게 됩니다. 이 단계에서 구버전의 캐시를 정리하는 작업을 수행할 수 있습니다.
서비스 워커는 Fetch API를 사용해 네트워크 요청을 가로채고, 이를 캐시에 저장할 수 있습니다.
서비스 워커는 백그라운드에서 푸시 알림을 수신하고, 이를 사용자에게 표시할 수 있습니다.
백그라운드 동기화를 통해 오프라인 상태에서 사용자가 수행한 작업을 네트워크가 복원될 때 서버와 동기화할 수 있습니다.
if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/service-worker.js').then(function(registration) { // 등록 성공 console.log('ServiceWorker registration successful with scope: ', registration.scope); }, function(err) { // 등록 실패 console.log('ServiceWorker registration failed: ', err); }); }); }
TypeScript
복사
const CACHE_NAME = 'v1'; const urlsToCache = [ '/', '/styles/main.css', '/script/main.js' ]; // 설치: 캐시를 열고 원하는 파일들을 캐시에 추가합니다. self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); }); // 활성화: 구 캐시를 정리합니다. self.addEventListener('activate', function(event) { var cacheWhitelist = ['v1']; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) ); }); // 네트워크 요청 가로채기: 캐시된 응답을 반환하거나, 네트워크에서 가져옵니다. self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // 캐시에서 찾았으면 해당 응답을 반환, 아니면 네트워크에서 요청 return response || fetch(event.request); }) ); }); // 푸시 이벤트가 발생하면 사용자에게 알림을 보낸다. self.addEventListener('push', function(event) { const title = 'Push Message'; const options = { body: 'This is a message from your push notification.', icon: '/icon.png', badge: '/badge.png' }; event.waitUntil(self.registration.showNotification(title, options)); }); // 네트워크 연결이 불안정할 때 사용자의 작업을 저장했다가 네트워크가 다시 연결되면 동기화 self.addEventListener('sync', function(event) { if (event.tag == 'myFirstSync') { event.waitUntil(doSomeBackgroundSync()); } }); function doSomeBackgroundSync() { // 여기에서 실제 동기화 작업을 수행합니다. // 예를 들어, 오프라인 상태에서 사용자가 생성한 데이터를 서버로 보낼 수 있습니다. return fetch('/some-endpoint', { method: 'POST', body: JSON.stringify({ /* 데이터 */ }), headers: { 'Content-Type': 'application/json' } }).then(response => { // 동기화 작업이 성공했을 때의 처리 }).catch(err => { // 동기화 작업이 실패했을 때의 처리 }); }
TypeScript
복사

웹 푸시 구현하기

웹 푸시 기능은 웹 워커 중 한종류인 서비스 워커를 통해 구현할 수 있으며, 브라우저의 백그라운드 환경에서 처리됩니다.
모던 브라우저에는 웹 푸시 알림을 제공하기 위한 API들이 구현되어 있으며, 푸시 서비스를 구독하고, 메시지를 수신하기 위한 Push API, 그리고 알림을 사용자에게 노출하기 위한 Notification API가 있다.

흐름도

1.
서비스 워커 등록
2.
사용자에게 알림 허용 요청
3.
Push API를 통해 사용자의 브라우저를 Push 서비스에 구독 시키며, 이 과정에서 생성되는 구독정보는 서버에 저장된다.
a.
리액트 컴포넌트에서 사용자가 어떠한 클릭을 할 때 Notification API를 통해 이루어 져야한다.
b.
사용자가 알림 수신을 동의하면, Push API를 사용하여 푸시 구독을 생성합니다. 이때 생성된 구독 정보는 서버에 저장된다.
4.
웹 애플리케이션 서버는 저장된 구독 정보를 사용하여 푸시 서비스에 알림을 보낼 것을 요청합니다. 이 요청에는 알림의 내용과 함께 사용자를 식별할 수 있는 정보가 포함됩니다.
node js 혹은 스프링부트에 저장된 구독 정보를 사용하여 푸시 서비스에 알림을 전송하는 기능을 구현합니다.
서버는 알림을 보낼 내용을 구성하고, 구독 정보에 해당하는 각 사용자의 기기로 알림을 전송하는 로직을 포함해야 합니다.
5.
푸시 서비스는 서버로부터 알림 요청을 받고, 해당 사용자의 기기로 알림을 전송합니다. 이 과정은 사용자의 브라우저가 오프라인일 때도 작동하며 ,기기가 온라인 상태가 되면 전송됩니다.
실제 푸시 서비스
6.
사용자의 기기에서 서비스 워커는 푸시 서비스로부터 알림을 수신합니다. 서비스 워커는 이 알림을 처리하는 로직을 실행할 수 있으며, 필요에 따라 사용자에게 알림을 표시할 수 있습니다.
7.
서비스 워커는 Notification API를 사용하여 사용자의 기기나 브라우저에 알림을 표시합니다. 사용자는 이 알림을 클릭하여 애플리케이션에 직접 이동할 수 있습니다.

Notification API

브라우저에서 시스템 알림을 띄우기 위한 기능을 제공하며, 여러 플랫폼에 맞는 시스템 알람을 쉽게 띄울 수 있게 해줍니다.
Notification API의 대표적인 기능은 다음과 같습니다.
// 알람의 권한 확인 Notification.permission; // 'default', 'denied', 'granted' Notification.requestPermission().then(function(permission) { if (permission === "granted") { console.log("알림 허용"); // 알림을 보내는 로직을 실행 } else if (permission === "denied") { console.log("알림 거부"); // 알림 거부 처리 로직을 실행 } else { console.log("알림 권한 요청을 무시함"); // 사용자가 권한 요청을 무시한 경우의 처리 로직을 실행 } }); // 사용자에게 알림 권한 요청 Notification.requestPermission().then(function(permission) { if (permission === "granted") { // 알림 권한이 허용되면 알림 생성 var notification = new Notification("새 알림", { body: "알림 메시지 본문입니다.", icon: "notification_icon.png", tag: "sample-notification", data: { importantData: "중요한 데이터" } }); // 알림 클릭 이벤트 처리 notification.onclick = function(event) { console.log("알림이 클릭되었습니다.", event); window.open("https://yourwebsite.com", "_blank"); }; // 알림 닫힘 이벤트 처리 notification.onclose = function(event) { console.log("알림이 닫혔습니다.", event); }; } });
TypeScript
복사
예제 코드
속성
title: 알람의 제목을 나타냅니다. (필수)
body: 알람의 본문 내용입니다.
icon: 알람에 표시될 아이콘의 URL입니다.
tag: 알림의 식별자로 사용됩니다. tag를 사용하면 동일한 tag를 가진 알림이 여러개 생성되지 않고, 대신 마지막 알림이 표시됩니다. (알림의 중복방지)
data: 알림과 관련된 데이터를 저장하는데 사용됩니다.
메서드
close(): 알림을 프로그래밍 방식으로 닫습니다.
이벤트
notification.onclick, notification.close 이벤트를 통해서 사용자의 상호작용을 감지할 수 있습니다.
권한이 허가 상태인 경우 알림을 띄울 수 있게 된다. 알림을 띄우는 방법은 크게 2가지가 있는데 알림 객체를 생성하는 것과 서비스 워커의 showNotification 메소드를 호출하는 것이다.
if (Notification.permission === "granted") { var notification = new Notification("알림 제목", { body: "알림 내용입니다.", icon: "icon_url_here.png", tag: "알림 태그" // 알림을 구분하기 위한 태그 }); // 알림 클릭 이벤트 처리 notification.onclick = function() { window.open("https://yourwebsite.com"); }; }
TypeScript
복사
Notification 객체 생성
// 서비스 워커 등록 if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js').then(function(registration) { console.log("서비스 워커 등록 성공"); }).catch(function(err) { console.log("서비스 워커 등록 실패:", err); }); } // 서비스 워커 알람 보내기 self.addEventListener('push', function(event) { if (Notification.permission === "granted") { self.registration.showNotification("알림 제목", { body: "알림 내용입니다.", icon: "icon_url_here.png", tag: "알림 태그" }); } });
TypeScript
복사
서비스 워커 등록 & 알람 보내기

Push API

Push API는 웹 애플리케이션에 서버로부터 메시지를 비동기적으로 전송할 수 있는 기능을 제공합니다. 이 API는 백그라운드에서 실행되는 서비스 워커를 통해 작동하며, 웹 애플리케이션이 활성 상태가 아니거나, 사용자가 다른 탭을 보고 있을 떄도 알림을 받을 수 있게 해줍니다.
웹 애플리케이션이 푸시 메시지를 수신하기 위해서는 활성화되어 있는 서비스 워커가 필요하다. 서비스 워커는 브라우저 백그라운드 환경에서 동작하고, 푸시 메시지를 수신했을 때 이벤트 핸들러를 통해 수신한 메시지를 처리할 수 있다.
Push API의 주요기능은 다음과 같다.
navigator.serviceWorker.ready.then(function(registration) { if (!('pushManager' in window)) { return; } registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(applicationServerPublicKey) }).then(function(subscription) { console.log('푸시 알림 구독 성공:', subscription); // 서버에 구독 정보를 보냄 }).catch(function(err) { console.log('푸시 알림 구독 실패:', err); }); });
TypeScript
복사
푸시 알림 구독하기(푸시 서비스 구독요청)
navigator객체는 브라우저의 정보와 관련된 다양한 속성과 메서드를 제공하는 웹 API의 일부분이다.
ex) 서비스 워커 지원 여부
registration은 서비스 워커의 등록을 나타내며, pushManager를 통해 사용자는 푸시 알림에 대한 구독을 생성/제거 할 수 있습니다.
userVisibleOnly: 푸시 알림이 사용자에게 항상 표시되어야 함을 의미한다.
applicationServerKey: 푸시 서비스가 메시지를 해당 사용자에게만 보내도록 암호화된 키를 제공한다. 보통 웹애플리케이션 서버에서 생성되며, 푸시 알림을 위해 필요하다.
// sw.js 파일 내부 self.addEventListener('push', function(event) { var options = { body: '이것은 푸시 알림 메시지입니다.', icon: 'images/icon.png', vibrate: [100, 50, 100], data: { dateOfArrival: Date.now(), primaryKey: '2' } }; event.waitUntil( self.registration.showNotification('푸시 알림 제목', options) ); });
TypeScript
복사
서비스 워커에서 푸시 이벤트 처리하기(푸시 이벤트 감지)
self.addEventListener('notificationclick', function(event) { event.notification.close(); event.waitUntil( clients.openWindow('https://example.com') ); });
TypeScript
복사
상호작용 처리하기 (알람 클릭시 이벤트)

서비스 워커 등록하기

푸시를 수신하기 위해 앞서 서비스 워커 등록이 필요하다. 서비스 워커는 다른 웹 워커와 마찬가지로 워커 스크립트를 로드하여 설치 & 활성화 할 수 있다.
navigator.serviceWorker.ready.then(function(registration) { if (!('pushManager' in window)) { return; } });
TypeScript
복사
async function registerServiceWorker () { if (!('serviceWorker' in navigator)) return; // 이미 등록되어있는 정보 가져오기 let registration = await navigator.serviceWorker.getRegistration(); if (!registration) { // 없으면 서비스 워커 등록 registration = await navigator.serviceWorker.register('/service-worker.js'); } }
TypeScript
복사
navigator.serviceWorker.getRegistration, navigator.serviceWorker.ready을 통해 서비스 워커의 등록 객체를 가져올 수 있다.

푸시 서비스 구독하기

서비스 워커가 등록되었다면 등록 객체를 통해 푸시 매니저에 접근할 수 있다.
registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VAPID_PUBLIC_KEY }).then(function(subscription) { console.log('푸시 알림 구독 성공:', subscription); // 서버에 구독 정보를 보냄 }).catch(function(err) { console.log('푸시 알림 구독 실패:', err); });
TypeScript
복사
푸시 매니저에 구현되어 있는 subscribe 메소드를 통해 푸시 서비스에게 구독 요청을 보낼 수 있고, 이 과정에서 알림을 수신하기 위해 브라우저가 사용자에게 권한을 확인하는 절차를 수행한다.
구독 요청을 위해 subscribe을 호출하는 경우 반드시 사용자 제스처가 필요한데, 이는 개발자가 악의적으로 푸시 서비스에 구독함을 방지하기 위함이다. (ex 버튼 클릭)
구독 요청을 보내기 위해서는 서버를 식별하기 위한 VAPID 공개키를 등록해야 하는데,

푸시 서비스 구독 취소하기

푸시 메시지 보내기 & 받기

동작 확인