크롬 익스텐션 너두 할 수 있어!

HyunHo Lee·2022년 7월 27일
7

JavaScript

목록 보기
19/20
post-thumbnail

Chrome Extension이란?

우리는 유용하거나 재밌는 익스텐션들을 chrome 웹 스토어에서 골라 적용한다. 혹시 익스텐션에 대해 잘 모르는 사람들을 위해 익스텐션이 무엇인지 간단하게 알아보자.


위 사진은 크롬 웹 스토어에서 검색하여 적용한 color picker라는 익스텐션이다. 현재 보고있는 브라우저 화면에서 추출하고 싶은 컬러가 있는 부분을 클릭하면, 해당 컬러를 hex값으로 뽑아준다.

여러 사이트를 방문하다가 마음에 드는 색이 있다면 저장해놓을 수 있는 것이다. 프론트엔드 개발자나 웹 디자이너가 사용하기에 편리한 것 같다.


개발만을 위한 목적으로 사용하는 익스텐션들도 있다. 예를들어 React에서 전역 상태 관리를 위해 리덕스를 사용하면 Redux DevTools이라는 익스텐션이 유용하다. 프로젝트의 여러 동작들에서 상태 관리가 어떤 플로우로 이루어지고 있는지 쉽게 파악할 수도록 도와준다. (React Developer Tools도 매우 유용하다!)


이 외에도 게임같은 단순 재미를 위한 익스텐션도 있다. 익스텐션에는 무궁무진한 카테고리가 있다고 보면 된다.


본격적으로 익스텐션 알아보기

익스텐션 구조

크롬 익스텐션은 일반 프론트엔드의 Web 프로젝트와 달리 신기한 구조를 가지고 있다. contentscript.js, popup.html, popup.js, background.js가 익스텐션의 준비물이다.


content script

현재 보고 있는 페이지에서 F12 키를 눌러 개발자도구를 확인해보자. 브라우저에 렌더링된 html, css, script등을 볼 수 있고, 이러한 요소들들로 Web이 어떻게 구성되어 있는지 분석할 수 있다. 내가 보고 있는 Web의 UI에서 첫 번째 이미지만 골라 border 속성을 부여해보자. const firstImg = document.querySelector("img")로 첫 번째 이미지를 렌더링된 DOM에서 찾고, firstImg.style.border="5px solid #000" 으로 스타일을 부여해주면 될 것이다.


(velog의 초록색을 빨강으로 변경)

content script를 이용하면 현재 페이지의 DOM에서 특정 Element의 정보를 얻거나, 얻은 정보로 DOM을 조작할 수 있게 해준다. 꼭 Element의 정보를 얻지 않아도 현재 페이지에 createElement 로 Element를 생성하여 원하는 UI를 추가할 수도 있다.


popup.html은 익스텐션을 클릭했을 경우 나타나는 UI다. 한번 더 클릭하면 사라지는 말그대로 팝업 형태이다. 처음에 언급했던 color picker를 보면, 클릭할 경우 색상이 저장될 팔레트 UI가 나타난다. 이와 같은 것을 popup.html에 작성하는 것이다.

첫 번째 이미지에 border속성을 부여하는 익스텐션을 로그인 한 유저만 사용할 수 있도록 설계한다고 가정해보자. 먼저 popup.html의 form태그 안에 로그인 정보를 입력할 수 있는 input들을 작성할 것이다. popup.js에서는 input들의 value를 핸들링하고, form태그의 submit 속성을 사용하여 입력한 정보들을 제출할 경우 어떤 행동을 취할지 결정할 수 있다.

그렇다면 이 input 데이터로 api endpoint에 GET 요청하여 인증 절차를 수행하는 부분은 어디서 설계하면 좋을까? 내 생각에는 background.js이다.


background

background.js는 브라우저 영역에서 작동하는 스크립트이다. contentscript는 현재 탭에서 특정 작업을 하기 때문에 브라우저마다 1회 로드된다면, 백그라운드는 탭의 갯수와 상관없이 한개만 존재한다. 크롬 익스텐션 1개당 백그라운드도 1개라는 의미이다.

popup.js에서 인증 절차를 거쳐도 되지만, 어차피 인증을 통과하고 받은 토큰이나 로그인 정보를 이용하여 CRUD를 하는 작업을 background에서 할 것이므로 인증도 그냥 background에서 하자. (관심사의 분리!)


또한, popup에서의 정보와 contentscript에서의 정보가 모두 필요한 HTTP 요청도 있을 수 있다. 결국 background에 모아 사용하게 되므로 이러한 부분을 background에서 처리하기로 결정한 것도 있다. 마지막으로 background에서는 cross-origin 요청도 가능하다.


manifest.json

{
  "name": "my extension",
  "version": "1.0",
  "description": "Build an Extension!",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": {
    "matches": ["http://*/*", "https://*/*"],
    "js": ["contentscript.js"]
  },
  "permissions": ["storage"],  
  "browser_action": {    
    "default_icon": "favicon.png",    
    "default_popup": "popup.html", 
  }    
}

이제 contentscript, popup, background 등을 사용한다는 것을 정의하고, 앱의 정보를 나타내줄 manifest.json을 먼저 작성하자. matches에는 어떤 사이트에서 이 익스텐션을 사용가능하게 할지 정하면 된다.


그리고 나의 크롬 익스텐션에 들어가서 "압축해제된 확장 프로그램을 로드합니다"를 클릭해준다. 이 파일이 있는 곳을 선택해주면 익스텐션이 등록된다.


스크립트간 통신

이제 우리는 popup, contentscript, background에서 어떤 작업을 할 수 있을지 감을 잡았다. 그런데 익스텐션은 개념만 보면 서로 다른 3개의 웹이 각각 존재하는 것 같다. 팝업에 대한 웹, 현재 보고있는 탭의 정보를 얻거나 요소를 추가할 수 있는 웹, 크롬 익스텐션의 백그라운드에서 HTTP Request를 하는 웹 말이다.

그렇다면 이 3개의 스크립트 파일끼리 데이터를 어떻게 주고 받을까?


sendMessage

const data = "익스텐션~~"

    chrome.runtime.sendMessage(
      {
        action: "sendData",
        data
      },
      (response) => {
        console.log(response);
      }
    );

익스텐션에서는 Chrome API를 이용하여 스크립트간 통신 한다. contentscript에서 Element에 대한 정보를 찾아 background로 넘기고 싶을 때 chrome.runtime.sendMessage를 사용한다. action에 string값을 넣어주는 이유는 받는 측에서 구분하기 위함이다.


addListener

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action == "sendData") {
    const { data } = request;
    sendResponse({ res: data });
  }
});

contentscript에서 보낸 데이터를 background에서 받기 위해서는 chrome.runtime.onMessage.addListener를 사용한다. 로그를 출력해보면 익스텐션~~이 보일 것이다. 이제 background에서 data를 사용할 수 있게 되었다!

나의 경우 contentscript에서 querySelect로 Element를 찾은 후 개발자가 원하는 동작을 수행해서 데이터를 background 측에 넘겨서 많이 사용했다.


tabs

    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      chrome.tabs.sendMessage(
        tabs[0].id,
        {
          type: "I am popup",
          yourData: "data..."
        },
        (response) => {
          console.log(response);
        }
      );
    });

popup에서 contentscript에 데이터를 넘기고 싶은 경우는 tab 정보를 사용해야한다. 왜냐하면 contentscript는 브라우저 탭마다 존재하기 때문에 어떤 탭에 정보를 넘길지 선택해야히기 때문이다.

contentscript에서 이 메세지를 받기 위해서는 chrome.runtime.onMessage.addListener를 사용하면 된다.


그 외

contentscript에서 popup이나 background에서 contentscript 등의 통신 방법은 설명하지 않았다. 왜냐하면 똑같이 sendMessageaddListener를 사용하면 되기 때문이다.


마무리

오늘은 바닐라 자바스크립트를 이용해서 크롬 익스텐션을 설계하는 방법을 알아보았다. 사실 매일 React만 사용하다가 바닐라로 무언가를 하려다 보니 쉽지 않았다. 예를 들면, fetch를 사용한 통신은 request body를 담기 위해서 body: JSON.stringify(...)를 사용한다.


"SyntaxError: Unexpected token < in JSON at position 0"

또한, response는 상황에 따라 res.json() 등의 작업이 필요하다. 무조건 .json을 붙인다면 위의 경고를 볼 수 있을 것이다. axios만 사용하던 나는 이번 작업을 통해 처음보는 에러를 많이 마주친 것 같다. 이 외에도 관심사의 분리 측면도 쉽지 않았다. 그래서 다음에는 React로 익스텐션을 설계하는 방법을 소개할것이다.

profile
함께 일하고 싶은 개발자가 되기 위해 달려나가고 있습니다.

0개의 댓글