네이버 사다리와 함께 했던 워크숍 (3)
취미로 하는 개발

네이버 사다리와 함께 했던 워크숍 (3)

저번 편에서 사다리를 원하는 대로 그리기 위해 minified 된 js 파일을 파헤쳐봤다.

https://nookpi.tistory.com/198

 

이번 편에서는 기존 사다리 게임 js 파일을 내가 수정한 js 파일로 교체하는 방법에 대해 알아보자.

 

원래는 네이버 검색으로 노출되는 사다리 게임 페이지 자체를 클론코딩(...)하려고 했으나, 동료분의 조언으로 크롬 익스텐션을 통해 주입하는 방법으로 선회했다. 이 방법이 훨씬 간단할 뿐 아니라 눈치채기도 어렵다...!

 

유일한 단점이라면 익스텐션이 사전에 설치되어 있어야 한다는 점인데 어차피 클론코딩으로 제공하는 페이지 역시 동일한 이슈가 있기 때문에 망설일 이유는 없었다.

 

내가 누구? 크롬 익스텐션 보일러 플레이트 오너

 

 

GitHub - Jonghakseo/chrome-extension-boilerplate-react-vite: Chrome Extension Boilerplate with React + Vite + Typescript

Chrome Extension Boilerplate with React + Vite + Typescript - Jonghakseo/chrome-extension-boilerplate-react-vite

github.com

 

바로 포크 후 익스텐션을 만들어보자.

 

유저가 보는 페이지에서 실행이 되어야 하기 때문에 실행 컨텍스는 콘텐츠 스크립트로 제한된다.

불필요한 다른 페이지들은 다 지우고, content 폴더만 남겨둔다.

 

1편 마지막에 파악했듯, 새로운 사다리 게임을 호출하기 위해서는 전역 객체인 window에 있는 require 메서드를 사용해야 한다. 콘텐츠 스크립트의 실행 환경은 "ISOLATED"와 "MAIN"으로 나눠지는데, 각 환경에서 접근 가능한 리소스의 범위가 다르다.

 

"ISOLATED" 환경에서는 chrome api에 접근이 가능하지만, 타 모듈에서 선언한 전역 객체 등에 접근할 수 없다.

반면, "MAIN" 환경에서는 전역 객체에 접근이 가능하지만 chrome api에 접근이 불가능하다.

 

 

chrome.scripting  |  API  |  Chrome for Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. chrome.scripting 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 설명 chrome.scripting API를 사용하여 다양한

developer.chrome.com

 

 

동적으로 특정 js 파일의 경로를 얻기 위해서는 chrome.runtime.getURL api를 사용해야 하니 "ISOLATED"환경이 필요하고, 그 스크립트를 로드하려면 "MAIN" 환경에서 실행되는 스크립트가 필요하다.

 

둘 다 사용하고 postMessage를 통해 소통하면 되잖아라고 생각했다면? 정답이다.

 

 

매니페스트 설정에서 한 스크립트의 world를 "MAIN"으로 바꿔준 다음, "ISOLATED" 환경에서 사다리 파일의 경로를 메시지로 쏴주자.

 

https://github.com/Jonghakseo/injected-sadari/blob/main/src/pages/content/ui/app.tsx#L12C1-L20C7

window.postMessage(
  {
    type: 'LADDER_URL',
    url: chrome.runtime.getURL('ladder.js'),
    victim,
    target,
  } satisfies InitMessage,
  '*',
);

 

 

받는 곳에서는 url을 받아서 실행만 해주면 간단하다.

 

https://github.com/Jonghakseo/injected-sadari/blob/main/src/pages/content/injected/index.ts#L2-L12

 

window.addEventListener('message', event => {
  if (event.data.type === 'LADDER_URL') {
    const message: InitMessage = event.data;
    window.__injected_sadari_victim = message.victim;
    window.__injected_sadari_target = message.target;
    window._nx_js_load(message.url, function () {});
  }
});

 

참고로 _nx_js_load 메서드는 require([url]) 과 동일한 역할을 한다.

 

사다리 파일도 살짝 수정이 필요했는데, 아래와 같이 타깃을 초기화하는 로직과 기존 캠버스를 삭제하고 새 캠버스를 생성하는 로직을 넣었다.

 

//! 전역으로 공유
let VICTIM = window.__injected_sadari_victim;
let TARGET = window.__injected_sadari_target;

//! 기존 캔버스 삭제
document.getElementById('ladders_canvas').remove();
//! 새로운 캔버스 생성
const newCanvas = document.createElement('canvas');
newCanvas.id = 'ladders_canvas';
newCanvas.width = 580;
newCanvas.height = 320;
document.getElementById('ladders').appendChild(newCanvas);

// 아래는 기존 사다리 코드 + 원하는 결과 나오도록 수정한 부분 포함
// ...

 

 

완성..!

 

 

이후 고도화를 통해 로컬 서버를 띄워놓고 실시간으로 타깃과 키워드를 바꿀 수 있게 추가로 구현했으나... 아쉽게도 그 기능을 쓸 상황은 나오지 않았다...ㅋㅋㅋ

 

저녁 식사 설거지에 당첨된 부대표님이 주섬주섬 고무장갑을 끼는 모습이 안타까워 조작된 사다리라는 걸 다 실토해 버렸다. 역시 장난은 준비하면서 키득거리는 시간이 더 즐거운 것 같다. :)

그래도 덕분에 워크샵에서 부대표님 노래도 듣고... 근래 했던 장난 중에 재일 재미있었다.