웹 푸시를 사용할 때의 문제점 중 하나는 푸시 메시지를 트리거하는 것이 매우 '까다롭다'는 점입니다. 푸시 메시지를 트리거하려면 애플리케이션이 웹 푸시 프로토콜에 따라 푸시 서비스에 POST 요청을 해야 합니다. 모든 브라우저에서 푸시를 사용하려면 VAPID(애플리케이션 서버 키라고도 함)를 사용해야 합니다. 기본적으로 애플리케이션이 사용자에게 메시지를 보낼 수 있음을 증명하는 값이 포함된 헤더를 설정해야 합니다. 푸시 메시지로 데이터를 전송하려면 데이터를 암호화해야 하며 브라우저에서 메시지를 올바르게 복호화할 수 있도록 특정 헤더를 추가해야 합니다.
푸시 트리거의 주요 문제는 문제가 발생하면 문제를 진단하기 어렵다는 점입니다. 시간이 지남에 따라 브라우저 지원이 확대되면서 이 문제가 개선되고 있지만 쉽지는 않습니다. 따라서 라이브러리를 사용하여 푸시 메시지의 암호화, 형식 지정, 트리거를 처리하는 것이 좋습니다.
라이브러리가 하는 일을 자세히 알아보려면 다음 섹션을 참고하세요. 지금은 구독을 관리하고 기존 웹 푸시 라이브러리를 사용하여 푸시 요청을 실행하는 방법을 살펴보겠습니다.
이 섹션에서는 web-push Node 라이브러리를 사용합니다. 다른 언어에는 차이가 있지만 그다지 다르지 않습니다. Node.js는 JavaScript이며 독자가 가장 간편하게 액세스할 수 있어야 하기 때문입니다.
단계는 다음과 같습니다.
- 구독을 백엔드로 전송하고 저장합니다.
- 저장된 구독을 검색하고 푸시 메시지를 트리거합니다.
구독 저장
데이터베이스에서 PushSubscription
을 저장하고 쿼리하는 방법은 서버 측 언어와 데이터베이스 선택에 따라 다르지만 수행 방법의 예를 살펴보는 것이 유용할 수 있습니다.
데모 웹페이지에서는 간단한 POST 요청을 통해 PushSubscription
가 백엔드로 전송됩니다.
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription),
})
.then(function (response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function (responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
데모의 Express 서버에는 /api/save-subscription/
엔드포인트에 해당하는 요청 리스너가 있습니다.
app.post('/api/save-subscription/', function (req, res) {
이 경로에서는 요청이 정상적이고 가비지가 가득하지 않은지 확인하기 위해 구독을 검증합니다.
const isValidSaveRequest = (req, res) => {
// Check the request body has at least an endpoint.
if (!req.body || !req.body.endpoint) {
// Not a valid subscription.
res.status(400);
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
error: {
id: 'no-endpoint',
message: 'Subscription must have an endpoint.',
},
}),
);
return false;
}
return true;
};
구독이 유효하면 이를 저장하고 적절한 JSON 응답을 반환해야 합니다.
return saveSubscriptionToDatabase(req.body)
.then(function (subscriptionId) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({data: {success: true}}));
})
.catch(function (err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message:
'The subscription was received but we were unable to save it to our database.',
},
}),
);
});
이 데모에서는 nedb를 사용하여 구독을 저장합니다. 이는 간단한 파일 기반 데이터베이스이지만 원하는 데이터베이스를 사용할 수 있습니다. 설정이 필요하지 않으므로 이 방법만 사용합니다. 프로덕션에서는 더 안정적인 것을 사용하는 것이 좋습니다. 저는 오래된 MySQL을 사용하는 경향이 있습니다.
function saveSubscriptionToDatabase(subscription) {
return new Promise(function (resolve, reject) {
db.insert(subscription, function (err, newDoc) {
if (err) {
reject(err);
return;
}
resolve(newDoc._id);
});
});
}
푸시 메시지 보내기
푸시 메시지 전송의 경우 사용자에게 메시지를 보내는 프로세스를 트리거하는 이벤트가 필요합니다. 일반적인 접근 방식은 푸시 메시지를 구성하고 트리거할 수 있는 관리 페이지를 만드는 것입니다. 하지만 로컬에서 실행할 프로그램을 만들거나 PushSubscription
목록에 액세스하고 코드를 실행하여 푸시 메시지를 트리거할 수 있는 다른 접근 방식을 사용할 수 있습니다.
데모에는 푸시를 트리거할 수 있는 '관리자와 유사한' 페이지가 있습니다. 이것은 단지 데모이므로 공개 페이지입니다.
데모를 실행하는 데 필요한 각 단계를 살펴보겠습니다. 노드를 처음 접하는 사용자를 포함하여 누구나 따라할 수 있는 초보 단계입니다.
사용자 구독에 관해 설명할 때 applicationServerKey
를 subscribe()
옵션에 추가하는 방법을 다루었습니다. 백엔드에서 이 비공개 키가 필요합니다.
데모에서는 이러한 값이 다음과 같이 Node 앱에 추가됩니다(지루한 코드이지만 마법이 아니라는 점을 알려드리고자 합니다).
const vapidKeys = {
publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};
다음으로 노드 서버용 web-push
모듈을 설치해야 합니다.
npm install web-push --save
그러면 노드 스크립트에서 다음과 같이 web-push
모듈이 필요합니다.
const webpush = require('web-push');
이제 web-push
모듈을 사용할 수 있습니다. 먼저 web-push
모듈에 애플리케이션 서버 키를 알려야 합니다. VAPID 키라고도 합니다(사양의 이름이기 때문).
const vapidKeys = {
publicKey:
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};
webpush.setVapidDetails(
'mailto:web-push-book@gauntface.com',
vapidKeys.publicKey,
vapidKeys.privateKey,
);
'mailto:' 문자열도 포함되어 있습니다. 이 문자열은 URL 또는 mailto 이메일 주소여야 합니다. 이 정보는 푸시를 트리거하는 요청의 일부로 웹 푸시 서비스로 전송됩니다. 이는 웹 푸시 서비스에서 발신자에게 연락해야 할 경우 이를 가능하게 하는 정보가 있기 때문입니다.
이제 web-push
모듈을 사용할 준비가 되었습니다. 다음 단계는 푸시 메시지를 트리거하는 것입니다.
이 데모에서는 가상 관리 패널을 사용하여 푸시 메시지를 트리거합니다.
'푸시 메시지 트리거' 버튼을 클릭하면 백엔드에서 푸시 메시지를 전송하라는 신호인 /api/trigger-push-msg/
에 POST 요청이 전송되므로 이 엔드포인트에 대한 라우트를 express에서 만듭니다.
app.post('/api/trigger-push-msg/', function (req, res) {
이 요청이 수신되면 데이터베이스에서 구독을 가져와 각각에 대해 푸시 메시지를 트리거합니다.
return getSubscriptionsFromDatabase().then(function (subscriptions) {
let promiseChain = Promise.resolve();
for (let i = 0; i < subscriptions.length; i++) {
const subscription = subscriptions[i];
promiseChain = promiseChain.then(() => {
return triggerPushMsg(subscription, dataToSend);
});
}
return promiseChain;
});
그러면 triggerPushMsg()
함수가 웹 푸시 라이브러리를 사용하여 제공된 구독에 메시지를 전송할 수 있습니다.
const triggerPushMsg = function (subscription, dataToSend) {
return webpush.sendNotification(subscription, dataToSend).catch((err) => {
if (err.statusCode === 404 || err.statusCode === 410) {
console.log('Subscription has expired or is no longer valid: ', err);
return deleteSubscriptionFromDatabase(subscription._id);
} else {
throw err;
}
});
};
webpush.sendNotification()
를 호출하면 프로미스가 반환됩니다. 메시지가 성공적으로 전송되면 프로미스가 확인되고 아무 조치도 취할 필요가 없습니다. 프로미스가 거부되면 오류를 검사해야 합니다. 이 오류를 통해 PushSubscription
가 여전히 유효한지 알 수 있습니다.
푸시 서비스의 오류 유형을 확인하려면 상태 코드를 확인하는 것이 가장 좋습니다. 오류 메시지는 푸시 서비스마다 다르며 일부는 다른 메시지보다 유용합니다.
이 예에서는 '찾을 수 없음' 및 '존재하지 않음'의 HTTP 상태 코드인 상태 코드 404
및 410
를 확인합니다. 이 중 하나가 수신되면 정기 결제가 만료되었거나 더 이상 유효하지 않은 것입니다. 이러한 시나리오에서는 데이터베이스에서 구독을 삭제해야 합니다.
다른 오류가 발생하면 throw err
만 실행하면 됩니다. 그러면 triggerPushMsg()
에서 반환된 프로미스가 거부됩니다.
웹 푸시 프로토콜을 자세히 살펴볼 때 다음 섹션에서 다른 상태 코드에 대해서도 다룹니다.
구독을 반복한 후 JSON 응답을 반환해야 합니다.
.then(() => {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({ data: { success: true } }));
})
.catch(function(err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({
error: {
id: 'unable-to-send-messages',
message: `We were unable to send messages to all subscriptions : ` +
`'${err.message}'`
}
}));
});
주요 구현 단계를 살펴보았습니다.
- 웹페이지에서 백엔드로 구독을 전송하는 API를 만들어 데이터베이스에 저장합니다.
- 푸시 메시지 전송을 트리거하는 API(이 경우 가짜 관리 패널에서 호출된 API)를 만듭니다.
- 백엔드에서 모든 구독을 검색하고 웹 푸시 라이브러리 중 하나를 사용하여 각 구독에 메시지를 전송합니다.
백엔드(Node, PHP, Python 등)와 관계없이 푸시를 구현하는 단계는 동일합니다.
다음으로, 이러한 웹 푸시 라이브러리는 정확히 어떤 역할을 할까요?
다음에 수행할 작업
- 웹 푸시 알림 개요
- 푸시의 작동 방식
- 사용자 구독하기
- 권한 UX
- 웹 푸시 라이브러리를 사용하여 메시지 보내기
- 웹 푸시 프로토콜
- 푸시 이벤트 처리
- 알림 표시
- 알림 동작
- 일반 알림 패턴
- 푸시 알림 FAQ
- 일반적인 문제 및 버그 신고