Velog 새 글 알림 기능 (제작편)

junah·2022년 8월 14일
38

Velog Helper

목록 보기
2/4
post-thumbnail

github : https://github.com/junah201/velog-helper

★ 요약
1. 벨로그에서 새 글이 왔을 때 알림이 안오는 것이 불편해서 크롬 확장 프로그램을 직접 만들기로 하였다.
2. 처음에는 백엔드를 Flask를 이용해서 만들었지만, 계속해서 반복되는 task를 실행시키기에 적절한 모듈이 없어서 구현이 힘들었음.
3. 유튜브에서 FastAPI 홍보 영상을 보고 어? 하고 FastAPI로 구현을 성공함, 자동 Docs 생성이 넘 좋았다.
4. 크롬 확장 프로그램을 처음 만들어봐서 많이 헤맸지만 결국 만드는 것에 성공함.
5. github PR은 항상 환영합니다.

※ 경고
1. 글이 개발하면서 써서 그런지 매우 중구난방합니다.
2. 글을 아무 생각없이 써서 아무 생각없다는 것이 티가 납니다.
3. 수많은 뻘짓이 기록되어 있으므로 답답함을 유발할 수 있습니다.
4. 현직 개발자분께서 보시기에 매우 비효율적인 코드로 불편함을 유발할 수 있습니다.
5. 글에 맞춤법이 틀렸을 수도 있습니다.

크롬 확장은 어떻게 만드는거지..?

지금까지 단 한번도 크롬 확장프로그램은 만든 적이 없어서 아무 생각이 없었지만 대충 JS로 만들겠지 하는 생각으로 시작하였다. 원래 모르는 것이 있을 때는 구글에 How to make ~~~

크롬 확장프로그램 공식 문서와 각종 글들을 찾아본 결과 크롬 확장 프로그램은 원래 웹사이트와는 다르게 package.json이 아나라 manifest.json을 기준으로 실행된다는 것을 알았고, 처음 추측했던 것처럼 JS로 동작한다는 것을 알았다.

공식 문서의 Getting Started를 보며 차근 차근 따라하기 시작하였다.

기획과 다르게 구현된 점

기존 기획에서는 RSS 주소를 이용하였지만, Velog API를 제공하는 것으로 확인되어 Velog API를 이용하는 방식으로 변경하였다.

프로그램 구조 - 프론트엔드

제 크롬 확장 프로그램에서 직접적으로 실행되는 파일은 background.js 뿐이고, 아래 조건에서 알림 버튼을 생성해주는 notice.js와 북마크 버튼을 생성해주는 notice.js을 실행시켜줍니다.

  1. 크롬 탭이 업데이트되었을 때 (onUpdated)
  2. 크롬 탭이 교체되었을 때 (onReplaced)

background.js에서는 위 두가지 경우에서 notice.jsbookmark.js를 실행시키는데

첫번째 onUpdated는 맨처음 사이트 로딩 혹은 새로고침 시 작동하는 것이고,

두번째 onReplaced 때문에 고생을 많이 했는데, onReplacedFired when a tab is replaced with another tab due to prerendering or instant. 때 실행되는데 즉 Velog는 SSR (서버 사이드 렌더링)을 사용하고 있기 때문에 이렇게 하지 않으면 사이트 내부에서 이동을 할 때 이벤트가 실행되지 않아 버튼 추가가 안됩니다.

onReplaced를 찾기 위해서 크롬 탭 이벤트 Docs에서 모든 이벤트를 하나 하나 찾아보면서 찾게 되었다.

// background.js
async function onUpdated(tabId) {
  const tab = await chrome.tabs.get(tabId);
  console.log(tab);
  if (tab.status == "complete" && /^http/.test(tab.url)) {
    console.log(123132);
    chrome.scripting
      .insertCSS({
        target: { tabId: tab.id },
        files: ["./src/notice.css"],
      })
      .then(() => {
        chrome.scripting
          .executeScript({
            target: { tabId: tab.id },
            files: ["./src/notice.js"],
          })
          .then(() => {
            console.log("SUCCES add notice button");
          });
      })
      .catch((err) => console.log(err));
  }
  // 개인 블로그 메인 페이지 라면?
  if (
    tab.status == "complete" &&
    /^http/.test(tab.url) &&
    tab.url.split("/").slice(-1) != "" &&
    tab.url.split("/").length === 4
  ) {
    chrome.scripting
      .insertCSS({
        target: { tabId: tab.id },
        files: ["./src/bookmark.css"],
      })
      .then(() => {
        chrome.scripting
          .executeScript({
            target: { tabId: tab.id },
            files: ["./src/bookmark.js"],
          })
          .then(() => {});
      })
      .catch((err) => console.log(err));
  }
}

chrome.tabs.onUpdated.addListener(async (tabID, changeInfo, tab) => {
  await onUpdated(tabID);
});

chrome.tabs.onReplaced.addListener(async (addedTabId, removedTabId) => {
  await onUpdated(addedTabId);
});

또한 notice.jsbookmark.js에서 백엔드에 데이터 요청을 할 일이 있으면 바로 요청하지 않고, background.jssendMessage를 통해 요청을 보내면 background.js에서 백엔드에 fetch 요청을 통해 데이터를 받아온 후에 sendResponse를 통해 데이터를 notice.jsbookmark.js로 보내줍니다.

(notice.jsbookmark.js에서 직접 fetch 요청을 보내도 될 것 같은데, 제가 찾은 예제에서 background.js를 통해서 통신하는 것을 보고 이와 같은 형식으로 구성함. 아마 로직의 분리..? 그런 느낌인 것 같은데 살짝 굳이 라는 느낌이 있다...)

// notice.js
noticeRefreshButton.addEventListener("click", () => {
  bookmarkButton.classList.add("yellow-bookmark-button");
  chrome.runtime.sendMessage(
    {
      message: "add_bookmark",
      payload: window.location.pathname.substring(2),
    },
    (response) => {
      if (response.message === "success") {
        console.log("success add bookmark");
      }
    }
  );
});
// bookmark.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.message === "add_bookmark") {
    chrome.storage.local.get("user", (data) => {
      fetch(
        `${Constants.BACKEND_URL}/add_bookmark?user_id=${data.user}&blog_id=${request.payload}`,
        {
          method: "POST",
        }
      )
        .then((response) => {
          return response.json();
        })
        .then((data) => {
          sendResponse({ message: "success" });
          return;
        });
    });
    return true;
  }
});

프로그램 구조 - 백엔드

맨처음에 백엔드는 Flask로 구성할 생각이였지만, Flask에서는 스케줄된 tasks를 구성하기 어렵기도 했고, 유튜브에서 FastAPI 홍보하는 영상을 보고 FastAPI를 해보고 싶어서 FastAPI로 구성해보았다.

백엔드는 단순히 요청을 받으면, MySQL DB에서 데이터를 생성, 수정, 전송, 삭제을 구현해놓았다. 자세한 코드는 깃허브에서

작동 모습

위쪽에 알림 버튼이 생깁니다.

알림 버튼을 누르면 새 글 목록이 뜹니다.

블로그에서는 즐겨찾기 버튼이 생깁니다.

배포

크롬 확장 프로그램

크롬 확장 프로그램을 등록하기 위해 등록비 5달러를 지불하고 등록하였다. 각종 정보를 적은 후 승인을 기다리고 있다.

백엔드 배포

원래는 배포할 때 항상 aws를 사용했었는데 heroku에서 https 로 자동 배포해주는 것이 너무 편해서 heroku에 배포하였다.

TO DO (추가할 것)

색깔 이슈 (수정완료)

살짝 내려갔다 올라오면 생기는 고정 헤더 에서는 색깔이 정상적으로 적용되지 않음

유저가 크롬 로그인을 하지 않았을 경우

chrome.runtime.onInstalled.addListener(() => {
  chrome.identity.getProfileUserInfo(function (userInfo) {
    // 로그인 되어 있지 않은 유저일 경우
    if (userInfo.email === "") {
      // TODO: 모르겠다 나중에 개발해야지
    }
    // 로그인 되어 있는 유저 일 경우
    else {
      console.log(userInfo);
      chrome.storage.local.set({
        user_id: userInfo.id,
        user_email: userInfo.email,
      });
    }
    // 백엔드에 유저 등록
    chrome.storage.local.get(["user_id", "user_email"], (data) => {
      fetch(`${Constants.BACKEND_URL}/user`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          id: data.user_id,
          email: data.user_email,
        }),
      }).then((response) => console.log(response));
    });
  });
});

유저가 크롬 로그인을 하지 않았을 경우 유저를 구분하는 방법이 명확하지 않아 정상 작동하지 않는데

유저가 로그인 하지 않을 경우 설치 시간, 기기 등의 특별한 값들을 기반으로 임시 아이디를 부여해야함.

북마크 버튼 광클 시 버그

북마크 버튼을 광클하면 노란색으로 북마크가 되어 있다고 나오는데 실제로는 북마크가 안되는 버그가 있다.
물론 새로고침하면 정상적인 값이 출력된다.

모바일 지원

모바일에서는 레이아웃이 깨지거나 하는 문제가 존재한다.

북마크된 블로그 목록을 볼 수 있는 추가 탭

크롬 확장에서 Velog Helper를 클릭시 나오는 창에 아직은 아무 것도 없지만

블로그 목록 등을 수정할 수 있는 창으로 제작할 예정이다.

크롬 확장 설치 시 추가 탭

크롬 확장이 설치되었을 때 사용법을 안내하는 튜토리얼 탭이 필요해보인다.

개발 후기

크롬 확장은 처음 만들어서 많이 헤맸지만 그래서 결국 완성해서 뿌듯했다. 사실 개발을 시작할 때만 해도 크롬을 사용하였지만, FireFox로 메인 브라우저를 변경해서 크롬 확장을 만드는 의미가 살짝 퇴색되었지만 기존 코드를 이용해서 FireFox에도 업로드하려고 한다.

profile
개발자를 꿈꾸는 사람

3개의 댓글

comment-user-thumbnail
2022년 8월 17일

바로 설치했어요

1개의 답글
comment-user-thumbnail
2022년 8월 24일

Interesting topic. LOL Beans is online game where you and real players all over the world can take part in racings overcoming obstacles and reaching the finishing line in a certain amount of time.

답글 달기