한줄요약 : 취준하다 답답해서 직접 만든 구인 공고 필터 크롬 확장 프로그램
chrome.google.com/webstore/detail/rma/ibfoelalnhihfkhhjpnpkajhmiojmnph?hl=ko&
목차
3-2. chrome.storage.sync : userDate 관리
1. 제작 동기 & 결과물
여러 취업 포털을 통해 입사지원 하던 가운데 중복, 기 지원, 관심 없는 공고로 인한 생산성 저하를 느꼈다.
취업포털 사이트를 한 개만 쓰는 게 아니니까 그 불편이 배로 더해졌다.
평소 크롬 브라우저를 주 브라우저로 사용하고 광고 차단 확장 프로그램을 사용하다보니
내가 등록한 회사의 공고는 DOM을 조작해줘서 가독성을 높여주는 프로그램이 있으면 좋겠다 싶었다.
그래서 이틀에 거쳐 직접 만들었다.
아래는 시연영상과 소스코드다.
https://github.com/HJ-Rich/RMA
2. 구상
시작하며 뭐가 필요할지 정리를 해봤다.
1. 접속한 페이지가 워크넷, 사람인, 잡코리아, 인크루트(이하 넷을 합쳐 취업포털) 인지 인식하기.
2. 해당 사이트에 접속했을 때만 내가 작성한 코드가 수행되도록 설정
3. 필터값을 저장, 삭제할 수 있어야 하고 껐다 켜도 유지돼야 하고 나아가 기기간에 동기화해주는 기능이 필요
4. 등록된 회사명을 공고 목록에서 찾을 경우 DOM 조작하기
대강 이렇게 네 가지가 핵심 기능이었다.
구상을 하고 나니 필터값을 동기화하는 방법이 가장 문제였다.
쿠키를 사용해야 하나? 저장하는 서버를 구축해야 하나? 쿠키를 사용하면 동기화가 안될텐데?
등의 걱정이 있었지만 일단 낮은 단계에서 구현을 하고 그다음 고도화 하자는 생각으로 시작했다.
3. 개발 시작
생활코딩의 강의도 있었고 다른 외국 유튜버들의 강의도 있었지만
이번엔 왠지 오기가 생겨서 문서를 보고 스스로 학습해서 구현하고 싶었다.
그래서 아래 링크로 시작했다.
developer.chrome.com/extensions/getstarted
크롬 확장프로그램의 기본 구조는 다음과 같다.
1. manifest.json 파일에 어떤 프로그램인지, 어떻게 작동할 것인지 등의 설정을 선언한다.
2. popup.html 파일에 아이콘 클릭시 나타날 팝업화면을 코딩한다.
크게 저렇게 두 개의 파일이 필요하고
그 외에도 특정 url에 접속시 해당 HTML문서 맥락에서 수행할 js파일을 manifest.json에 선언한다던가
확장프로그램의 아이콘을 선언하는등 부수적인 선언이 있다.
기본 구조를 잡은 다음엔 배포전에 로컬에서 바로 사용하며 테스트할 수 있다.
개발자 도구를 활성화시킨 확장 프로그램 관리 탭에서 manifest.json이 담긴 폴더를 추가해주면 된다.
여기까지만 하면 개발과 테스트를 진행할 준비가 끝난 셈이다.
다시 생각해보면 정말 간단하게 개발, 테스트, 적용을 할 수 있게 되어 있다.
IE는 이제 아예 배제되는 추세라서 최근 브라우저 트렌드에선 그리 가벼운 브라우저는 아니지만
역시 크롬이 대단하다고 느껴진다.
3-1. manifest.json : 환경설정
json 타입을 직접 코딩하는 것은 처음이었다.
공부해가며 작성하다보니 주석을 작성하려 했는데
json에는 보통 주석을 사용하지 않는 것이 권장된다 하여 모두 삭제했다.
content_scripts 에 속하는 값으로 matches와 js가 있는데
matches에는 url 타입을, js는 타입이 매칭되었을 경우 해당HTML 문서 맥락에서 수행할 js파일을 선언한다.
가령 이번 확장 프로그램의 경우 취업포털에서만 사용하기 때문에
url타입을 4개 사이트의 https와 http로 총 8개를 잡았다.
js에는 location.host를 . 으로 split 한 값중 1번째 인덱스를 기준으로 삼아 어느 사이트인지 인지하도록 짰다.
가령 워크넷이면 https://www.work.go.kr 에서 work가 나온다.
다른 사이트에 접속했을 경우엔 alert이 안되고 matches에 선언한 사이트에 접속했을 때만 alert이 되는걸 확인했다.
여기까지 해서 구상의 1,2번은 대강 구현이 된 셈이다.
현재 url을 인식하여 지정된 url에 해당할 경우 해당 HTML문서 맥락에서 js가 수행되는 것이다.
manifest.json 소스코드
{
"name": "RMA",
"description": "Remember My Application",
"version": "1.0.0",
"icons": {
"128": "128.png"
},
"permissions": [
"storage"
],
"content_scripts": [
{
"matches": [
"http://www.work.go.kr/*",
"http://www.saramin.co.kr/*",
"http://www.jobkorea.co.kr/*",
"http://job.incruit.com/*",
"https://www.work.go.kr/*",
"https://www.saramin.co.kr/*",
"https://www.jobkorea.co.kr/*",
"https://job.incruit.com/*"
],
"js": [
"common.js"
]
}
],
"manifest_version": 2,
"browser_action": {
"default_title": "RMA",
"default_icon": "RMA.png",
"default_popup": "index.html"
}
}
3-2. chrome.storage.sync : userData 관리
다음으로는 구상에서 3번째 기능이었던 필터값을 저장하고 동기화하고 사용하는 것이다.
처음엔 쿠키를 사용해야 하나 혹은 서버를 따로 사용해야 하나 고민이 되었다.
브라우저를 껐다 켜도 유지가 되어야 하고, 가급적 기기가 달라도 크롬 로그인이 동일하다면
유지가 될 수 있으면 했다.
그런데 chrome.storage 에서 확장프로그램 별로 userData를 동기화할 수 있는 기능이 준비되어 있었다.
chrome.storage에는 sync, local, manage가 있는데 결론만 말하자면 읽기전용의 값이 아니라면 sync를 쓰면 된다.
공식문서에는 local에 비교되는 sync의 장점을 열심히 설명해뒀다.
크롬 로그인만 해두면 데이터가 동기화 되어 다른 기기에서도 동일한 경험을 할 수 있는 점,
오프라인 상태에서 userData가 추가될 경우 나중에 online이 되면 자동으로 동기화가 되는 점,
local은 스트링으로 저장하지만 sync는 객체로 저장하는 점 등이다.
추가적으로 암호화되지 않기 때문에 개인식별 정보는 저장하지 않아야 하며 비동기수행이다.
지금 보니 비동기라는 점을 문서를 읽을 땐 노션에 메모를 해놨는데 코딩할 땐 비동기인 걸 잊어서 삽질 좀 했다.
암튼 제일 구현하기 어려울 것 같았던 필터 값의 생성, 유지, 동기화는
chrome.storage.sync. set, get, remove, clear 로 아주 간단하게 구현됐다.
set으로 저장할 땐 키 : 밸류로 이뤄진 객체를 첫번째 인자로, 두번째 인자로 콜백함수를 준다.
get으로 불러올 땐 첫번째 인자로 키를 담은 배열을, 두번째 인자로 콜백함수를 준다.
get에서 첫번째 인자를 null로 전달하면 전부 가져온다.
remove로 지정 제거, clear로 전체 제거할 수 있으며
onChanged 를 addListener 하여 userData의 변화를 감지하여 콜백 함수를 수행할 수 있다.
이로서 가장 걱정했던 3번. 필터값 처리가 가능했다.
지금까지 kakapMap, FullCalendar, Summernote, Google Login, Sweet Alert 등의 API를 사용해봤는데
그중에서 기능에 비해 단연 가장 쉬운 개발과정이었다.
하긴 생각해보면... 위의 것들은 CRUD를 하기 위한 게 아니라 프론트단을 위한 거고
백은 내가 다 짰어야 했는데... chrome.storage는 백단 로직이 이미 다 있는 거니까.. 그런것 같다.
나중에 chrome.storage가 어떻게 구현되는 건지 이해하고 구현할 수 있게 되면 좋겠다.
index.js 소스코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
// Create : 새로운 필터 규칙 생성
document.getElementById('createKV').addEventListener('click', () => {
//회사명 입력
let company = prompt("회사명을 입력하세요");
if (!company) {
alert('사명이 입력되지 않았습니다');
return;
}
//설명 입력
let description = prompt(`${company} 에 대한 설명을 입력해주세요`);
if (!description) {
alert('설명이 입력되지 않았습니다');
return;
}
//회사명, 설명을 키 밸류로 하는 규칙 등록
chrome.storage.sync.set({ [company]: description }, function () {
location.reload();
alert(`'${company}' 회사에 대한 규칙 '${description}' 등록 완료`);
});
});
// Read : 필터 갯수 체크
document.getElementById('readKV').addEventListener('click', () => {
chrome.storage.sync.get(null, function (result) {
let kvArray = Object.entries(result);
// 필터 없으면 알려주고 리턴
if (kvArray.length === 0) {
alert('아직 필터가 존재하지 않습니다');
return;
}
// 필터 갯수와 모든 키 밸류 쌍 얼럿
alert(`${kvArray.length}개의 필터가 존재합니다.`);
});
});
// Clear : 모든 필터 제거
document.getElementById('clearKV').addEventListener('click', () => {
chrome.storage.sync.get(null, function (result) {
// 필터 없으면 알려주고 리턴
let kvArray = Object.entries(result);
if (kvArray.length === 0) {
alert('아직 필터가 존재하지 않습니다');
return;
}
// 필터 갯수를 알려주며 컨펌 후 제거
if (confirm(`${kvArray.length}개의 필터가 제거됩니다. 진행하시겠습니까?`)) {
chrome.storage.sync.clear(function () {
location.reload();
alert('제거 완료');
});
}
});
});
|
cs |
3-3. popup.html : View
manifest.json의 "browser_action" 객체 내에 있는
"default_popup" 에 선언되어 있는 html파일.
그게 곧 해당 크롬 확장 프로그램의 view단이다.
브라우저에서 아이콘을 클릭했을 때 나오는 창
그게 바로 default_popup이다.
왼쪽 그림과 같이.
아이콘을 이용해 소스 코드를 보러 이동할 수도 있고
개발자에게 문의하기용으로 사용될 수 있는 메일발송도 가능하다.
필터 추가를 이용해서 어떤 사명을 필터로 등록할 것인지, 해당 회사를 필터하는 이유가 무엇인지를 입력받게 했다.
필터가 되더라도 아예 해당 DOM 요소를 remove하는 것은 부적절하다고 판단했다.
첫째로 검색결과 갯수가 바뀌는 것은 화면 구조가 바뀌는 것이기 때문에 부적절하다고 생각했다.
둘째로 광고를 필터하는 게 아니기 때문에 어떤 요소가 필터되었는지 확인할 수 있어야 한다고 생각했다.
셋째로 혹시 발생할 수 있는 예외에 대비해 사용자가 최종적으로 확인하고 대응할 수 있어야 한다고 생각했다.
필터를 추가하거나 제거할 경우 리로드 없이 DOM 처리를 해줄까 고민하다가
편의성을 약간 포기하고라도 안정성을 가져가기로 판단했다.
추가하거나 제거하면 페이지를 리로드함으로써 새로 생긴 규칙을 적용시킨다.
규칙의 생성은 생성 버튼을 눌러서 사명과 사유를 입력할 수 있고
제거의 경우 너무 많아지면 어떤 규칙이 있는지 외울 수 없기 때문에 시각적으로 보여줄 필요도 있었다.
새로 페이지를 열까 생각했지만 그러면 코딩이 너무 복잡해지고 사용자 입장에서도
현재 보고 있는 페이지를 벗어나는데다가 새탭까지 열리니 완전 감점요소였다.
그래서 얼마전 학습해뒀던 datalist를 활용했다.
HTML5에서 새로 추가된 요소가 뭐가 있나 w3school을 둘러보다가 발견했다.
국비 최종프로젝트를 진행할 당시에는 다른 팀원분이 API를 찾아서 이런 기능을 구현했었는데
HTML5에서는 약간 투박하나마 아주 간단하게 지원이되고 있는 기능이었다.
입력창을 클릭해서 등록되어 있는 모든 값들을 확인할 수 있고,
그중 특정 값을 선택할 경우 값을 제거할지 확인 후 제거가 가능하다.
아직 존재하지 않는 필터값을 입력할 경우엔 새로 등록도 가능하다.
이것을 프로토타입으로 코딩하여 codepen으로 공유해본다.
이것과는 다를 수도 있지만 각종 검색창에서 사용되는 게 비슷한 원리가 아닐까 싶다.
onkeydown 또는 onkeyup 일때 ajax로 통신해서 값을 가져온 다음
option들로 넣어주는 것...
See the Pen datalist by Richard JEON (@hj-rich) on CodePen.
이로써 프론트 작업이 완료되었다.
최초 구상했던 1,2번인 url 인식과 실행, 그리고
3번인 필터값 등록 제거 동기화의 front, back이 모두 완료되었다.
이제 4번, DOM 조작만 남았다.
3-4. 소소한 삽질
DOM 조작은 opacity를 0.3으로 바꾸고 사용자가 입력해서 저장되어 있는
userData내의 필터사유를 덮어씌워주는 식으로 구현했다.
텍스트를 생성해서 해당 요소에 추가해주고 위치를 조금 만져줬다.
이제부터는 이 과정까지 있었던 소소한 삽질들 + 새로배운 것들에 대해 기록한다.
1. 새로 배운 문법 : Object.entries, keys, values
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
Object.keys(obj) – returns an array of keys.
Object.values(obj) – returns an array of values.
Object.entries(obj) – returns an array of [key, value] pairs.
chrome.storage.sync.get 으로 가져온 객체는 key, value 쌍으로 이뤄진 pair들을 갖고 있었고
이를 활용하기 위해 위와 같은 문법을 활용했다.
2. 새로 배운 entity name과 entity number.
저장된 키를 꺼내서 DOM과 비교를 하는 과정에서 나를 괴롭힌 문자들이 있었다.
그들의 entity number는 바로 ㈜ 과 ㈔ 이었다.
이녀석들은 바로 ㈜와 ㈔ 이다. 주식회사, 또는 사단법인의 약자다.
사이트마다 또 공고마다 (주) 이렇게 그냥 글씨와 괄호로 사용하는 곳도 있지만
위처럼 특수문자로 사용하는 경우가 있어서 동일 여부를 검증하는데에서 애를 좀 먹었다.
구글링하다가 < 이런 형식이 entity name이라는 것을 알게되어
entity name으로 검색을 해도 값이 너무 나오지 않았다.
그러다 entity number를 알게 되었다.
자주 쓰이지 않는 것들에 대해서는 entity name이 없는 것 같았다.
특히나 한글로 된 특수문자여서 그런지 더욱 안나왔다.
그래서 내가 한 행동은 바로 다음의 코드이다.
물론 고생은 내가 한 게 아니라 컴퓨터가 하긴 했다.
원래는 저기서 최대값에 0을 하나 더 붙였었는데 너무 오랫동안 반응이 없어서 하나 줄여서 재실행했다.
그리고 다행히 찾아냈다. 무려 1만 2천번대에서 말이다.
3만번대에서는 한자가 지나갔고
4만번대에서는 뀶뀷뀸뀹뀺 과 같은 글자가 지나갔고
7만번 직전에서 모든 문자가 길다란 네모모양으로 똑같아졌다.
검색해도 그렇게 안 나왔던 이유가..
이 몇만개나 되는 녀석들을 모두 웹페이지에 굳이 담아놓지 않는 것도 이해가 됐다.
앞으로도 잘 쓰이지 않는 특수문자의 entity number가 필요할땐 이런 과정을 거쳐야할 것 같다.
이게 별거 아닌거고 어떻게 보면 사소한 것인데 나의 시간과 노력을 좀 잡아먹었다.
유저입장에선 입력한 값이 입력한 대로 보여지고
검증할때만 replace 해서 하려다보니 시간이 걸렸다.
3. 정규식에 변수를 사용하기
주로 쓰던 방식이 (/검증할패턴/).test(검증대상) 이런 형식의 코드였는데
이런 형식의 코드는 변수를 사용하지는 못한다(아마도).
이럴떈 let myReg = new RegExp(변수); myReg.test(검증대상); 를 이용해 검증할 수 있다.
4. 문의 메일 작성 기능
a태그의 href값으로 "mailto:메일주소" 를 주면 클릭시
해당 운영체제에 기본값으로 설정되어있는 메일 앱으로 메일쓰기 창이 열리고
수신자주소에 메일주소가 자동으로 들어간다.
간단하면서도 유용하다 느껴졌다.
4. 완성 및 배포
완성된 프로그램을 배포하기 위해 크롬 웹스토어 개발자로 등록을 했다.
최초 등록 시 5달러를 내야 한다.
플레이스토어 개발자 등록비보다는 확실히 저렴하다.
5달러를 내고 들어간 첫 화면 대시보드에서 이런 문구가 있어서 적잖이 당황했다.
구글에게 5달러를 삥뜯긴건가 했는데..
"Chrome 앱"과 Chrome 확장 프로그램은 다른 것이었다.
분명 위의 개발자 등록 화면에서도 "Chrome 브라우저용 확장 프로그램 및 테마" 라고 했다.
Chrome 앱은 크롬에서 공식적으로 등록하는 어플리케이션으로 Chrome OS등에서 사용되는 것으로 파악됐다.
암튼 그래서 확장프로그램과는 상관 없다는 뜻.
등록 대기중이다.
꽤나 귀찮은 입력항목이 많았다.
5종류 정도 되는 서로 다른 pixel에 정확히 맞춘 스크린샷을 업로드해야 했고
영상 링크를 첨부하는 곳이 있어서 영상을 찍고 유튜브에 올려서 링크를 작성했다.
앱에 대한 설명과 Google Analytics ID도 입력했고
storage와 match 권한이 필요한 이유에 대해서도 설명해야 했다.
그리고 나서 드디어 검토 대기중이 될 수 있었다.
Java 미니 프로젝트로 캐치마인드를 만들었고
Spring 최종 프로젝트로 위치기반 매칭 시스템을 만들었었지만
이번에 만든건 뭔가 정말 나를 개발자인 듯한 기분이 들게 해준다.
내가 필요해서 직접 만들고. 기능하게 하고.
그것을 공식적인 공간에 배포되길 기다리고 있다.
아직 부족함이 있어서 업데이트하긴 해야겠지만
이 뿌듯함(이라 쓰고 뽕맛이라 읽는다)을 느끼기 위해
또 공부하고 또 스트레스 받고 또 성취해야겠다.
5. 후기
1. 뿌듯하다.
취업포털을 해메이다가 중복되는 공고를 걸러내는데
나의 메모리를 사용한다는 사실에 현타가 와서 개발하게 됐는데.
아예 새로운 API를 문서를 읽어가며 학습해서 이틀만에 미약하나마
완성을 해서 배포 단계까지 했다는 게 뿌듯하다.
2. JavaScript는 역시 재밌다.
나는 Java, Spring 위주로 기술스택을 깊이 팔 계획으로
node나 php, python 등 다른 기술스택을 요구하는 회사는 아예 지원을 않고 있는데.
처음 공부할 때부터 느꼈지만 JavaScript는 참 재밌는 언어다.
아직 웹 개발자로 첫 걸음마를 떼는 나에게는 DOM 조작을 할 수 있는 유일한 언어라는 사실이
거의 모든 것을 할 수 있는 언어처럼 느껴져서 그런 것 같다.
JavaScript를 나에게 알려주신 생활코딩, 드림코딩 엘리 두 분께 감사하다.
클립수가 꽤 많았던 생활코딩의 JavaScript 강좌를 국비과정 동안 다 들었던 게
나에게 정말 큰 기초체력이 되어줬다.
엘리님의 강좌는 눈이 띠용 하며 튀어나오는 실전 사용법을 알려주셔서 감사했다.
3. 구글, Chrome 대단하다.
이 시스템이 얼마나 거대한지 가늠할 수 없지만
이렇게 뉴비도 문서보고 뚝딱뚝딱 만들어낼 수 있게 구축해둔 게 정말 감사하고 대단하게 느껴진다.
4. 포폴 쓸거리 하나 늘었으니 이력서 업뎃해서 지원하러 가야겠다...
모무들 취뽀 성공을 기원합니다!
끝.
2020.10.25 공개되었다!
chrome.google.com/webstore/detail/rma/ibfoelalnhihfkhhjpnpkajhmiojmnph?hl=ko&
'JavaScript' 카테고리의 다른 글
TypeScript 첫 발 떼기 (0) | 2021.01.11 |
---|---|
예약일 선택용 캘린더 구현하기 without 라이브러리 (0) | 2020.11.10 |
24시간 간격 방문 카운트 구현 : 쿠키, 정규식, navigator.userAgent (0) | 2020.10.04 |
모바일 메뉴 열기 닫기 (2) | 2020.10.02 |
기초적인 XSS 대응 (0) | 2020.10.01 |