PWA Push
사용자가 플랫폼을 사용하도록 유도하는 방법은,,,?
PWA는 Progressive Web Application으로 웹 페이지를 몇가지 간단한 설정만으로 앱처럼 사용할 수 있도록 만드는 기술이다.
기술을 사용하는 건 좋다. 사용자가 단순한 웹 페이지를 설치하여 PC, Mobile 구분 없이 사용할 수 있는 장점이 생기기 때문이다.
하지만 이 기술을 도입해서 웹 페이지가 더욱 활성화 될 것이라는 보장은 할 수 없다.
그렇다면 우리 고민은 그대로다. 어떻게 사용자가 우리의 플랫폼을 사용하도록 유도할 수 있을까
많은 플랫폼들은 Push 알림을 하나의 방법으로 사용하고 있다.
두 개의 API
Push API와 Notifications API는 독립된 API이지만, 플랫폼을 사용하도록 유도할 때 함께 잘 동작한다.
Push API는 클라이언트 사이드의 관여 없이 서버로부터 앱으로 새로운 컨텐츠를 전달하는데 사용되고 앱의 service worker에 의해 처리된다.
Notification은 service worker에 의해 사용자에게 새로운 정보를 보여주거나 무언가 업데이트 되었음을 알리는데 사용된다.
두 API는 브라우저 창의 바깥에서 동작하기때문에 앱의 페이지를 보고 있지 않아도, 종료가 되어도 푸시를 보내거나 알림을 보여줄 수 있다.
Notifications
Notification은 Push를 사용하지 않아도 동작한다. 먼저 독립적으로 사용해보자.
권한 요청
알림을 보여주기 위해서는 권한 요청이 필수다.
먼저 구글에 접속하여 개발자 도구를 사용해 아래의 코드로 권한을 받아보자.
Notification.requestPermission().then(result=>console.log(result))
실행 했더니 다음과 같이 브라우저에서 알림 표시 권한을 요청한다고 나온다.
차례대로 허용, 차단을 실행해봤다. granted
또는 denied
라고 나오는 것을 확인할 수 있다.
위 알림에 대한 승인을 받았을 경우 Push와 Notification 모두 동작할 수 있다.
알림 생성
MDN의 예시를 통해 randomNotification을 실행해보자.
간단하게 확인할 수 있도록 내용을 조금 수정했다.
이미지는 네트워크 탭에서 보이는 아무거나 골라서 넣었다.
function randomNotification() {
var notifTitle = "Notification Title";
var notifBody = "Notification Body";
var notifImg = "https://developer.mozilla.org/static/media/note-info.0eafb6e7738509bce66e.svg";
var options = {
body: notifBody,
icon: notifImg,
};
var notif = new Notification(notifTitle, options);
}
기본적으로 Title, Body, Domain, Image를 표시해 보았다.
SVG를 사용해도 이미지가 잘 나오는 것도 확인할 수 있었다.
Notifications API는 운영체제의 알림 기능을 사용한다는 장점이 있다.
이는 사용자가 웹을 보고있지않아도 알림을 보여줄 수 있고 네이티브 앱과 유사하다는 것을 의미한다.
Push
개요
Push는 앱으로 데이터를 전송해줄 서버를 구독해야 한다. 앱의 Service Worker는 Push 서버로부터 데이터를 수신하며 알림 시스템 또는 다른 매커니즘을 사용해 보여줄 수 있다.
따라서 Push 서버가 필요하다. 나는 서버가 없다. 로컬에서 직접 만들어 push를 구현해보려한다.
만약 실서비스를 적용하기위한 Push 서버를 찾고있다면 Firebase 서비스를 확인해보자.
여기서부터는 PWA의 service worker를 사용한다.
service worker가 구현되어있지않거나 잘 모른다면 아래 링크를 통해 PWA 예제를 만들고 다시 읽자.
실제 구현하기 전 Push Notification 구현의 구조를 살펴보자.
크게 구독과 Push로 나누었다.
구독
- 서버에서 생성한 Public Key를 받아와 구독을 생성한다.
- Public Key와 구독할 서버의 주소 등을 담아 구독을 생성한다.
- 구독 정보를 서버로 전송한다.
- 서버에서는 추후 Push를 구현 하기 위해 구독 정보를 DB에 저장한다.
Push
- DB에서 구독 정보를 가져와 확인한다.
- 구독 정보를 통해 Service Worker에 원하는 정보를 전달한다.
- 받은 정보를 가공하고 Notification API를 사용하여 사용자에게 표시한다.
구현
위에서 봤던 구조와 순서에 따라 실제로 구현해 볼 차례다.
먼저 프로젝트 폴더를 하나 만들고 들어가준다.
mkdir push_notification
cd push_notification
프로젝트에서 필요한 패키지를 설치한다.
npm i web-push express body-parser
vapid key를 발급하여 나만의 key를 만든다.
./node_modules/.bin/web-push generate-vapid-keys
아래와 같은 형식으로 출력이 되면 public key와 private key를 공개되지않은 곳에 저장한다.
=======================================
Public Key:
*************************
Private Key:
*************************
=======================================
index.js를 작성하여 Push Server를 만들어준다.
// 의존성 모듈을 가져온다.
const express = require('express');
const webpush = require('web-push');
const bodyParser = require('body-parser');
const path = require('path');
// 서버를 생성한다.
const app = express();
// 정적 파일을 제공한다.
app.use(express.static(path.join(__dirname, '')));
// body-parser 미들웨어를 사용한다.
app.use(bodyParser.json());
// Vapid keys
const publicVapidKey = '***Your Public Vapid Key***';
const privateVapidKey = '***Your Private Vapid Key***';
// Vapid keys를 설정한다.
webpush.setVapidDetails(
'mailto:***Your Email***',
publicVapidKey,
privateVapidKey
);
// Send Notification
app.post('/sendNotification', (req, res) => {
const subscription = req.body.subscription;
const payload = req.body.payload;
res.status(201).json({});
webpush.sendNotification(subscription, JSON.stringfy(payload)).catch(err => console.error(err));
});
// 포트를 설정하고 서버를 실행한다.
const port = 5002;
// 서버를 실행한다.
app.listen(port, () => console.log(`Server started on port ${port}`));
위 코드는 publicKey와 privateKey를 설정하고 sendNotification이라는 경로로 api 요청이 오면 해당 구독자에게 body.payload에 있는 정보를 보내는 코드이다. 꼭 *가 있는 문자열은 변경해야한다.
이제 동일한 폴더에 client code를 작성해주면 끝이다.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Push Notification</title>
</head>
<body>
<h1>Push Notification</h1>
<script src="client.js"></script>
</body>
</html>
client.js
const publicVapidKey = "***Your Public Vapid Key***";
// Check for service worker
if ("serviceWorker" in navigator) {
send().catch((err) => console.error(err));
}
// 서비스워커 등록, 푸쉬 등록, 푸쉬 보내기
async function send() {
// 서비스워커 등록
console.log("서비스워커 등록중...");
const register = await navigator.serviceWorker.register("/worker.js", {
scope: "/",
});
console.log("서비스워커 등록됨");
// 푸쉬 등록
console.log("푸쉬 등록중..");
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey),
});
console.log("푸쉬 등록됨");
console.log(subscription);
}
// Vapid key를 안전하게 base64 스트링을 Unit8Array로 변환..
function urlBase64ToUint8Array(base64String) {
var padding = "=".repeat((4 - (base64String.length % 4)) % 4);
var base64 = (base64String + padding)
.replace(/\-/g, "+")
.replace(/_/g, "/");
var rawData = window.atob(base64);
var outputArray = new Uint8Array(rawData.length);
for (var i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
worker.js
console.log("서비스워커 로드됨...");
self.addEventListener("push", (e) => {
const data = e.data.json();
self.registration.showNotification(data.title, data);
});
아래 명령어를 통해 서버를 시작하고 localhost:5002
에 접속해보자.
node index.js
접속 후 console을 확인해보면 subscription 정보가 있다.
복사 해두었다가 localhost:5002/sendNotification
호출 시 사용하면 된다.