[단기 프로젝트 #1] 에브리타임 청소기 구글 크롬 플러그인 토이 프로젝트 후기
반갑습니다람쥐. 다람쥐예요!
얼마 전 지인의 집에서 다 같이 해커톤을 한다는 소식을 들었어요.
그 날 휴가에다 오후에 일정이 있었어서 저녁에 스리슬-쩍 저도 찾아갔었어요. 😀
갑자기 뜬금 없는 구글 크롬 확장 플러그인?
예전부터 구글 크롬 플러그인이 어떻게 만드는 지 궁금해서 토이 프로젝트로 한 번 만들어보고 싶었어요.
서버 개발자다보니 주제가 딱히 떠오르지 않아 잠시 접어두었었는데요.
예전부터 이용하던 에브리타임에서 자동화 스크립트를 작성하여 사용하고 있었는데
매번 복사하고 텍스트로 관리하기 귀찮아서 토이 프로젝트로 구글 크롬 플러그인을 제작해보았어요.
부하를 일으킬 수 있는 특정 스크립트를 대신 실행 시키는 거라 따로 배포는 하지 않았어요!
완성 화면을 보여줘 !
구글 크롬 확장 플러그인 완성 화면입니다.
옵션을 설정할 수 있는 화면은 없고 플러그인을 클릭했을 때 나오는 팝업 화면으로만 구성했습니다.
H1 태그로 제목을 설정하고 Button 과 onclick 속성으로 버튼 동작을 구현하였습니다.
통계는 구글 크롬 스토리지 기능을 이용하여 내부 데이터를 관리했어요!
에브리타임 청소기 기능은 게시글을 모두 삭제하는 기능과 댓글을 모두 삭제하는 기능이예요.
일일이 하나씩 지워야 해서 번거로움을 없앴어요.
사실 모두 삭제하는 건 아니고 특정 페이지 또는 특정 갯수만 지웁니다. 🤣
다음으로 추천 요정 기능인데요. 현재 보고있는 게시판 페이지 글들을 모두 추천 한 번 씩 눌러줍니다.
끌올 기능으로 추천수 9인 묻힌 게시글을 찾아 추천을 눌러줘서 HOT 게시판으로 이동시켜줍니다.
모두 몇 년 전에 자바스크립트 공부하면서 심심풀이로 작성해뒀던 스크립트인데,
이번 기회로 플러그인으로 제작해보았어요. 😀
어떻게 확장 플러그인 프로젝트를 시작할 수 있어?
처음 확장 플러그인 프로젝트를 시작했을 때 구글 Extensions 문서의 Getting Started 문서를 참고하였어요.
위의 문서에서 데모 프로젝트를 소스 코드와 함께 제공해주는데요!
데모 프로젝트를 기반으로 확장 플러그인 프로젝트를 시작했어요.
사용하지 않는 images 폴더 등을 제거하여 아래와 같이 프로젝트 구조를 설정했어요.
확장 플러그인을 등록하기 위해 필요한 파일은 아래와 같아요!
- manifest.json : 확장 플러그인이 어떤 플러그인인지 기술하는 파일이에요!
- 확장 플러그인의 이름, 설명, 버전, 기본 팝업 HTML 파일, 기본 옵션 HTML 파일, 기본 플러그인 아이콘을 설정할 수 있어요.
- 구글 크롬 객체를 사용해서 현재 브라우저가 사용하는 정보도 얻어올 수 있어요! 각 기능을 사용하려면 어떤 권한을 사용할 것 인지 permissions 속성으로 나열해주어야 했어요!
- 이번 프로젝트에서는 특정 사이트에서만 사용할 서드 파티 라이브러리를 로드해야 했기에 content_scripts 속성을 지정해주었어요.
{
"name": "에브리타임 청소기",
"description": "에브리타임 게시글과 댓글을 모두 삭제합니다.",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"options_page": "options.html",
"content_scripts": [
{
"matches": ["https://everytime.kr/*"],
"run_at": "document_idle",
"js": ["/third_parties/jquery-1.10.2.min.js"]
}
]
}
- options.html : 확장 플러그인 우측 클릭하여 옵션을 클릭했을 때 나오는 화면
- popup.html : 확장 플러그인을 클릭했을 때 나오는 팝업 화면
- background.js : 백그라운드에서 실행되는 서비스 워커 파일
동작은 어떻게 하는거야?
기존에 쓰던 자바스크립트 코드만 분리하여 따로 실행시킬 수 있어서 무척 편리했어요.
scripts 폴더에 에브리타임 사이트에서 실행시킬 스크립트 파일들을 저장했어요.
아래는 게시글을 모두 삭제하는 버튼의 로직 코드예요!
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const [injectionResultAtCheckIfTitleIsEverytime] =
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["scripts/check_if_title_is_everytime.js"],
});
const isEverytimeTitle = injectionResultAtCheckIfTitleIsEverytime.result;
if (isEverytimeTitle === false) {
throw new Error("에브리타임 웹사이트를 켜주세요!");
}
const [injectionResultAtCheckIfAuthenticatedInEverytime] =
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["scripts/check_if_authenticated_in_everytime.js"],
});
const isEverytimeAuthenticated =
injectionResultAtCheckIfAuthenticatedInEverytime.result;
if (isEverytimeAuthenticated === false) {
throw new Error("게시글/댓글을 삭제할 수 없는 페이지입니다.");
}
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["scripts/delete_posts.js"],
});
현재 창의 정보를 얻을 수 있고 그 정보를 바탕으로 외부 스크립트를 실행시킬 수 있습니다.
chrome.scripting.executeScript 의 반환 방식이 특이했어요.
외부 스크립트 코드 안에서 return 문을 사용해서 반환하는게 아니라, 출력으로 값을 가져와서 특이했어요.
return 문으로 중간에 스크립트 실행을 종료하여 값을 가져오지 못해서 한 스크립트 파일 안에서 유효성 검사를 같이 진행할 수가 없었어요.
위 코드를 보시면 현재 사이트의 제목을 검사한다든지, 로그인이 되어있다든지 하는 등의 검사를 따로 스크립트 파일로 만들어 진행한 것을 볼 수가 있어요.
따라서 개별적으로 유효성 검사 스크립트를 실행하면서 그 결과값으로 오류를 반환하는 로직을, UI 버튼 이벤트 리스너에서 한꺼번에 작성했습니다!
예를 들어 'check_if_title_is_everytime.js' 유효성 검증 로직은 아래와 같이 작성되었어요.
function getTitle() {
return document.title;
}
var result = true;
if (getTitle() !== "에브리타임") {
window.alert("에브리타임 웹사이트를 켜주세요!");
result = false;
}
result;
이 스크립트의 result 변수의 출력 값은 injectionResultAtCheckIfTitleIsEverytime 상수 값으로 할당되게 됩니다.
let 으로 선언하지 않은 이유는 이 스크립트를 두 번 이상 실행시키면 똑같은 변수를 할당하지 못한다는 오류가 나타나 var 키워드로 변수를 선언했어요!
주요 기능은 모두 jQuey Ajax 기반으로 네트워크를 호출하는 로직이 전부입니다.
비동기로 여러 번 호출하기에 횟수를 카운팅하여 저장할 때 일반적인 로직으로 작성하면 동시성 문제로 카운팅이 안되는 이슈가 있었는데요! setTimeout 메서드로 타임아웃을 비례하여 늘려줌에 따라 동시성 문제를 어느정도 해소할 수 있었어요.
setTimeout(() => {
chrome.storage.sync.get("votePostCount", ({ votePostCount }) => {
chrome.storage.sync.set({
votePostCount: ++votePostCount,
});
});
}, 10 * multiplyTimeOut++);
구글 크롬 스토리지 기능을 사용해서 get 메서드와 그 콜백으로 votePostCount 변수를 저장하고 증가시키고 있어요.
이를 setTimeout 의 타임아웃을 달리 함으로써 같은 votePostCount 값을 저장하지 않게끔 방지하였어요.
위 로직을 가진 타이머를 많이 만들면 브라우저가 동작하지 않는 이슈가 발생할 수도 있지만,
아주 많은 타이머가 생성되는 건 아니라서 setTimeout 으로 해결을 했네요. 😄
테스트는 어떻게 했어?
테스트는 팝업 화면을 개발자 도구로 열어 콘솔 화면과 네트워크 탭으로 스크립트 동작이 잘 되는지, 비동기 호출은 잘 되는지 확인하면서 개발했어요!
코드를 수정하며 테스트할 때 기본적으로 새로 확장 플러그인을 브라우저에 로드하지 않아도 자동으로 코드가 저장되면 새로고침이 됩니다!
만약 중간에 안되면 우측 하단 새로고침 표시로 새로 로딩할 수도 있고요!
다만 직접 새로고침 버튼을 누르거나 플러그인을 삭제하면 저장된 스토리지들이 모두 삭제되니 조심하세요!
마무리
구글 크롬 확장 플러그인이 어떻게 동작되는지 늘 궁금했는데,
이번 토이 프로젝트를 하면서 생각보다 만들기 쉽다, 라는 걸 알 수 있었어요.
문서도 친절하고 예제 코드도 웬만한 기능은 들어가 있어서 처음 개발하는 사람도 조금만
아이디어에 맞게 커스텀만 해주면 되는 수준이었어요.
외부 스크립트를 실행하는 부분은 고생을 하긴 해서 생각보다 시간이 오래 걸렸지만
저녁을 먹고 새벽 1~2시에 다 만들고 저는 7시까지 꿀잠을 잤었네요. 😂
스리슬-쩍 무지성으로 참여한 저를 받아준 지인분들께 감사의 인사를 전합니다. 🙇♂️🙇♂️
다음 단기 프로젝트도 기대해주세요!