[실전 프로젝트] PWA를 활용한 Push 알림 구현
본문 바로가기
항해 중/8-13주차 실전 프로젝트

[실전 프로젝트] PWA를 활용한 Push 알림 구현

by 은돌1113 2022. 1. 10.

오늘의 일정

1. Asmr 페이지, AsmrPopUp 페이지 수정

  • 음원 최대 3개에서 최대 4개까지로 변경
  • AsmrPopUp 페이지에 예외 처리 추가 (새로고침 시 Asmr 페이지로 이동)
  • 음원 삭제 시 가장 처음에 있는 음원이 적용 안되는 문제 해결
    (따로따로 있던 delete 함수를 하나의 함수에 담고, 조건문을 사용하여 조건에 맞으면 삭제 되도록 수정했다.)
  • CSS 수정
    - Asmr.js → Inline Style 부분들 styled-component로 변경
    - AsmrPopUp.js → Inline Style 부분들 styled-component로 변경 + 기록이 없습니다. 변경

2. service-worker 적용 확인

 

- 1차 시도

2022.01.09 - [더 알아보기/기능] - PWA를 사용하여 push 알림 설정하기 (React + Node.js)

 

- 2차 시도

https://gist.github.com/ninanung/3c3520359abed543a2bb8e09e49212e4

 

React에서 FCM을 사용해봅시다.

React에서 FCM을 사용해봅시다. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

- 3차 시도 : 팀원분께서 해결하신 후 알려주셨다.

service-worker.js가 index.html과 다른 폴더에 있으면 연결이 안되는 이슈가 발생 → public 폴더에 담아 주셨다. → Push Companion에서 public key를 받아서 index.html에 key 넣어주고 → console에 찍힌 인증 코드를 Subscription to Send To에 넣어준다. → SEND PUSH MESSAGE를 누르면 알림이 뜬다. (만약, 알림이 안뜬다면 window 상에서 Chrome 알림 설정을 허용 해줘야 한다.

더보기
더보기
더보기
// public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

    <script>
      let appServerPublicKey =
        "BLWGR4hQ1GAXksMfhmbOSwspsWET47QI-K5defhvsvMrJhQtbobOQqk7QbC03z3T4BMojv8iQF6lIlspdqBZoFA";
      let swRegist = null;
      let isSubscribed = true;

      Notification.requestPermission().then(function (permission) {
        // if ("serviceWorker" in navigator) {
        if (Notification.permission === "granted") {
          navigator.serviceWorker
            .register("./sw.js") // 서비스워커 등록 public에 있는 sw.js 의 경로로 등록해줌
            .then((regist) => {
              swRegist = regist;
              test(swRegist);
              console.log("권한 승인해서 서비스워커 등록!");
              console.log(swRegist);
            }); // 권한 허용이 되었다면 실행
        }
        if (Notification.permission === "denied") {
          // 권한 차단 했을경우 실행
          console.log("권한 거부했음!");
          updateSubscription(null);
          return;
        }
        // }
      });

      const test = (swRegist) => {
        console.log(swRegist);
        swRegist = swRegist;

        const initPush = (isSubscribed) => {
          console.log(swRegist);
          // const pushButton = document.getElementById("subscribe");
          // pushButton.addEventListener("click", () => {
          // console.log(isSubscribed);
          // if (isSubscribed) {
          //   unsubscribe();
          // } else {
          subscribe();
          // }
          // }
          // );
          console.log(swRegist);
          swRegist.pushManager.getSubscription().then(function (subscription) {
            isSubscribed = !(subscription === null); // null 이면 true 이니 !true 가 false 로 해서 isSubscribed 가 false 라는뜻
            updateSubscription(subscription); // updateSubscription 함수로 구독 정보를 전달

            if (isSubscribed) {
              console.log("User IS subscribed.");
            } else {
              console.log("User is NOT subscribed.");
            }

            updateButton();
          });

          function subscribe() {
            let appServerPublicKey =
              "BLWGR4hQ1GAXksMfhmbOSwspsWET47QI-K5defhvsvMrJhQtbobOQqk7QbC03z3T4BMojv8iQF6lIlspdqBZoFA"; // 자신의 public key 넣어줘야함
            const applicationServerKey =
              urlBase64ToUint8Array(appServerPublicKey);
            swRegist.pushManager
              .subscribe({
                userVisibleOnly: true,
                applicationServerKey: applicationServerKey,
              })
              .then((subscription) => {
                console.log("User is subscribed.");
                updateSubscription(subscription);
                console.log(subscription);
                isSubscribed = true; // 구독정보를 반아온 경우 구독을 정상적으로 한 상황이므로 true로 변경
                updateButton();
              })
              .catch((err) => {
                console.log("Failed to subscribe the user: ", err);
                updateButton();
              });
          }

          //구독 버튼 상태 갱신
          function updateButton() {
            // TODO: 알림 권한 거부 처리
            if (Notification.permission === "granted") {
              // 권한 허용이 되었다면 실행
              // new Notification("푸시허용 테스트 알림!!"); // 알림 내용 그대로 보여줌
            }
            if (Notification.permission === "denied") {
              // 권한 차단 했을경우 실행

              updateSubscription(null);
              return;
            }

            // const pushButton = document.getElementById("subscribe");
            if (isSubscribed) {
              // pushButton.textContent = "Disable Push Messaging";
            } else {
              // pushButton.textContent = "Enable Push Messaging";
            }
            // pushButton.disabled = false; // true로하면 해당 버튼이 안눌리고 비활성화 된다.
          }

          // 구독 정보 갱신
          function updateSubscription(subscription) {
            // TODO: 구독 정보 서버로 전송

            // let detailArea = document.getElementById("subscription_detail");

            if (subscription) {
              console.log(JSON.stringify(subscription));
              // detailArea.innerText = JSON.stringify(subscription);
              // detailArea.parentElement.classList.remove("hide");
            } else {
              // detailArea.parentElement.classList.add("hide");
            }
          }

          //알림 구독 취소
          function unsubscribe() {
            swRegist.pushManager
              .getSubscription()
              .then((subscription) => {
                if (subscription) {
                  return subscription.unsubscribe();
                }
              })
              .catch((error) => {
                console.log("Error unsubscribing", error);
              })
              .then(() => {
                updateSubscription(null);
                console.log("User is unsubscribed.");
                isSubscribed = false;
                updateButton();
              });
          }

          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;
          }
        };

        initPush();
      };
    </script>
  </body>
</html>
// public/sw.js

window.self.addEventListener('install', pEvent => {
    console.log( "서비스워커 설치 함!")
  })
  
  // This allows the web app to trigger skipWaiting via
  // registration.waiting.postMessage({type: 'SKIP_WAITING'})
  window.self.addEventListener('message', (event) => {
    if (event.data && event.data.type === 'SKIP_WAITING') {
      window.self.skipWaiting();
    }
  });
  
  // Any other custom service worker logic can go here.
  window.self.addEventListener('push', function (event){  // push 이벤트나 service-worker.js 와 접점이 없는거 같다
    console.log('Push ' + event.data.text());
  
    const title = 'My PWA!';
    const options = {
      body: event.data.text()
    };
   
    event.waitUntil(window.self.registration.showNotification(title, options)); // showNotification을 통해 푸시 알림을 생성, Promise가 반환되며 waitUntil을 통해 이벤트를 연장 시켜야함
  });
  
  window.self.addEventListener('notificationclick', function(event) {
    console.log('Push clicked');
  
    event.notification.close();
  
    event.waitUntil(
      
        window.self.clients.openWindow('http://localhost:3000/')  // 예시로 일단 로컬호스트로 링크 누르면 가지는걸로 해놨다.
    );
  });

https://web-push-codelab.glitch.me/

 

Push Companion

This site is here to make it easy to get going with sending a push message, but it's obviously not helpful for sending push messages from your site. To send push messages from your own server, use one of the libraries available here.

web-push-codelab.glitch.me

3. S3에 https로 배포하는 과정 - 개떡 같은 정리 양해 바람

  • S3에 접속해서 버킷을 만든다. (총 2개를 만들어야 한다.)
    - 도메인 이름(ex, zzzapp.co.kr)(가비아에서 구매한 도메인)에 build한 파일을 넣는다.
    - www.도메인 이름(ex, www.zzzapp.co.kr)에 build 안한 파일을 넣고 정책 설정 안하고, redirection 설정, 호스팅 이름이 들어가 있다.
  • Route 53에 도메인을 넣어야 한다. (ex, zzzapp.co.kr을 넣으면 2개의 파일?이 생성된다.) 생성된 4개의 name을 가비아에 넣는다.
  • 인증 요청을 보내야 한다. 해당 도메인에 DNS 요청을 보내면 상태가 심사중으로 뜨고, 인증서 id로 record를 하나 더 생성 해줘서 Route 53에 갔을 때 더 생겨 있을 텐데 name을 만들고 나서 조금만 있으면 인증서 발급을 해준다.
  • 인증서가 활성화 되면 클라우트 프론트(Cloud Front)에 가서 배포 생성을 한다.
    도메인이 S3에 있는 게 자동으로 뜨는 데 build한 파일로 넣어주고 http or https로 선택하고, 인증서 땡겨오고, 대체 도메인에 zzzapp.co.kr을 넣어주고 배포하기를 누르면 배포가 끝난다.
  • 처음 배포한 사이트에 들어가면 403 에러가 뜨는 데 이건 클라우드 프론트(Cloud Front)에서 redirect 403으로 막혀 있다. (react-router-dom 때문이다.) 이거를 클라우드 프론트(Cloud Front)에 오류 페이지에 403오류 정보를 넣어주고 무효화를 시켜 주면 된다.

배포한 사이트

https://zzzapp.co.kr/

 

React App

 

zzzapp.co.kr

 

댓글