c9u11

PWA Push

사용자가 플랫폼을 사용하도록 유도하는 방법은,,,?

PWAProgressive Web Application으로 웹 페이지를 몇가지 간단한 설정만으로 앱처럼 사용할 수 있도록 만드는 기술이다.

기술을 사용하는 건 좋다. 사용자가 단순한 웹 페이지를 설치하여 PC, Mobile 구분 없이 사용할 수 있는 장점이 생기기 때문이다.

하지만 이 기술을 도입해서 웹 페이지가 더욱 활성화 될 것이라는 보장은 할 수 없다.

그렇다면 우리 고민은 그대로다. 어떻게 사용자가 우리의 플랫폼을 사용하도록 유도할 수 있을까

많은 플랫폼들은 Push 알림을 하나의 방법으로 사용하고 있다.

두 개의 API

Push APINotifications 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로 나누었다.

구독

  1. 서버에서 생성한 Public Key를 받아와 구독을 생성한다.
  2. Public Key와 구독할 서버의 주소 등을 담아 구독을 생성한다.
  3. 구독 정보를 서버로 전송한다.
  4. 서버에서는 추후 Push를 구현 하기 위해 구독 정보를 DB에 저장한다.

Push

  1. DB에서 구독 정보를 가져와 확인한다.
  2. 구독 정보를 통해 Service Worker에 원하는 정보를 전달한다.
  3. 받은 정보를 가공하고 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 호출 시 사용하면 된다.