티스토리 뷰
[ChromeExtensionDev] React, TypeScript로 github.com에서 추천해주는 Chrome Extension 만들기
SweetDev 2022. 6. 7. 17:31부스트캠프 AI Tech 3기 추천시스템 최종 프로젝트를 진행하며 개발했습니다.
이 글은 Chrome Extension을 만드는 방법에 대해서 소개하고, 주의해야 할 사항을 설명하는 포스팅입니다.
Intro
Chrome Extension을 개발하며 겪었던 보안 이슈에 대해서는 타 포스팅에 적었는데, 개발하는 전반적인 방법이나 공부한 것들에 대해서 정리하고 주의사항도 간단하게 적기 위해서 포스팅을 쓴다.
BoilerPlate
Github 에 있는 BoilerPlate를 사용해서 개발했다. 이 BoilerPlate의 Readme를 참고하면 사용법이 나와있는데, git clone을 받은 후
npm start로 빌드할 수 있고 코드를 수정하고, save하면 바로 또 빌드가 된다.
처음에 개발하기에 너무 막막해서 Udemy에 있는 React & Typescript Chrome Extension Developement(2022) 강의를 15000원에 결제해서 들었다. 나쁘지는 않았지만 어차피 Documentation을 꼼꼼하게 읽어가며 개발하게 되어있기 때문에 개발 왕초보가 아니면 추천하지 않는다. 위 BoilerPlate를 제공받은 점은 좋았다. (그런데 어차피 오픈소스라서..github에서 검색할 수 있었다.)
Chrome Extension 설치하기
boilerplate에서 나온 dist/ 폴더를 선택해서 올리면 된다. 이후 코드를 바꾸고 나서는 새로고침 버튼을 누르면 새로 반영이 된다.
코드
https://github.com/boostcampaitech3/final-project-level3-recsys-04/tree/main/front-end
Chrome Extension의 구조에 대해서 알아보자
Manifest.json파일
{
"name": "Github Recommendation Extension",
"description": "This library adds repository recommendation to Github.com",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"host_permissions": [ "<all_urls>", "https://*/*"],
"permissions": [
"storage",
"activeTab",
"webRequest",
"contextMenus",
"cookies",
"webNavigation",
"tabs",
"alarms"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "black-cat.png",
"32": "black-cat.png",
"48": "black-cat.png",
"128": "black-cat.png"
}
},
"icons": {
"16": "black-cat.png",
"32": "black-cat.png",
"48": "black-cat.png",
"128": "black-cat.png"
},
"content_scripts": [
{ "matches": ["<all_urls>"],
"js": ["contentScript.js"],
"exclude_matches": [],
"run_at": "document_idle"
}
],
"content_security_policy": {
"sandbox": "sandbox allow-scripts; script-src 'self' https://example.com"
},
"sandbox": {
"pages": [
"page1.html",
"directory/page2.html"
]
}
}
라이브러리에 관련된 정보를 모두 지정하는 곳이다.
Chrome Storage API
간단한 로컬 데이터를 저장하기 위해서 사용한다. 사용하기 위해서는 mainfest.json에 "storage"를 추가해줘야 한다.Local, Sync 두개가 있는데 여기서는 Sync를 사용했다.
// set
chrome.storage.sync.set({
name,
}, ()=> {
console.log(`Name is set to ${name}`)
})
// get
chrome.storage.sync.get([“name”, “test”],
(res)=> {
const name = res.name ?? “???”
nameElement.textContext = `어쩌꾸`
})
Background Scripts and Service Workers
background service worker는 항상 load되어 있지 않고 함수를 실행할때나 값을 가져올 때 생기게 된다.
마찬가지로 mainfest.json에 등록 해주어야 한다.
호출 타이밍은 다음과 같다.
- 익스텐션 설치 or 업데이트
- 백그라운드 페이지가 이벤트를 기다리는 중이었는데 이벤트 생김
- content script나 다른 extension이 메세지 보냄(Message Passing: chrome.runtime.sendMessage)
- 팝업같은 익스텐션의 다른 뷰가 runtime.getBackgroundPage한다.
Chrome Alarms API
Service worker가 sleep해도 쓸 수 있다.
// 이벤트 생성하기
chrome.alarms.create({
periodInMinutes: 1 / 60,
})
// 핸들링 하기
Chrome.alarms.onAlarm.addListener((alarm) => {
chrome.storage.local.get([‘timer’], (res)=>{
const time = res.timer ?? 0
})
})
Chrome Notifications API
크롬 앱에 푸시 오는 기능 구현할 때 쓴다.
마찬가지로 manifest.json에 등록해준다.
# manifest.json
permissions: [“notifications”] // 권한에 notifications 추가해주기
background.js
this.registration.showNotification("Chrome Timer Extension", {
body: `${notificationTime}`,
icon: "icon.png",
})
Chrome Search and Tab APIs
context menu: 텍스트 드래그 할 때 뜬다. 이걸로 google 검색 가능하게 된다.
manifest.json
"permissions": [
"search",
"tab",
] -> 검색 하고 새 탭에 띄워야 해서.
검색하기
chrome.search.query({
disposition: “NEW_TAB”,
text: event.selectedText,
})
현재 열려있는 탭 정보 전부 받아오기
chrome.tabs.query({
currentWindow: true, //
}, (tabs) => {})
새로 탭 만들고 이동하기
chrome.tabs.create({
url: “~~”
})
webNavigation
- 순서: onBeforeNavigate -> onCommitted -> [onDOMContentLoaded] -> onCompleted
- chrome.webNavigation.onCompleted : 웹 리소스가 전부 다 로드 되었을 때 콜 된다.
ContentScript
⭐️ content_scripts: Web page의 context에서 실행되는 파일들. 표준 DOM을 사용해서, Chrome에서 방문하는 웹 페이지를 읽어오고 바꿀 수 있다.⭐️ 가장 중요하고 내가 개발하는 라이브러리에서 핵심적이었지만, 나를 제일 고생시킨 파일이기도 했다... 주의사항을 잘 읽고 개발하기를 꼭! 권유한다.
injection에는 static과 dynamic이 있는데, 여기서는 static injection에 대해서 설명하려고 한다.
먼저 manifest.json에 정의해준다.
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contentScript.js"],
"exclude_matches": [],
"run_at": "document_idle"
}
],
ContentScript manifest.json 설정
- matches: content script가 어느 페이지에 삽입될지 명세한다.
- exclude_matches: 옵션. 현재 content script가 삽입되지 않을 페이지. 바로 위의 Match Patterns로 들어가서 문법을 더 확인하거나, 바로 위의 Match patterns and globs로 가서 어떻게 제외시킬지 보면 됩니다
- match_about_blank: 옵션. 현재 content script가 about:blank페이지나 about:srcdoc페이지에 삽입될지 안될지를 결정합니다. content script는 inherit url이 matches필드에서 규칙에 맞을 경우에 페이지에만 삽입됩니다. inherit url은 frame이나 window에 의해 생성되는 url을 말합니다. 하지만 sandboxed된 frame에는 삽입되지 않습니다. false를 디폴트값으로 가집니다
- js: 옵션. 매칭된 페이지에 삽입될 자바스크립트 파일의 목록입니다. 리스트 안에 있는 순서대로 삽입됩니다.
- run_at: 옵션. js파일이 삽입되었을때 컨트롤합니다.
- document_start, document_end, document_idle 일 수 있습니다. 기본값으로 document_idle를 가집니다.
- document_start일 경우에는 css파일이 삽입되고 난 후이지만 DOM이 구성되기 전이며 다른 스크립트가 실행되기 전에 삽입됩니다.
- document_end일 경우에는 DOM이 구성되고 난 후이지만 images나 frames가 로딩되기 전에 삽입됩니다.
- document_idle의 경우에는 브라우저가 'document_end'과 'window.onload 이벤트 파일이 생성된 후', 둘중에 한가지를 골라서 삽입합니다. 정확히 언제 삽입될지는 document가 얼마나 복잡한지, 로딩되는지 얼마나 걸리는지, 페이지가 로딩되는데 최적화에 달려있습니다.
- document_idle일때는 content scripts가 window.onload이벤트가 끝난 후에 실행될 수도 있어서 window.onload이벤트를 받을 필요가 없을수도 있습니다. 대부분의 경우에서 document_idle일 경우에 onload이벤트를 기다리는것은 DOM이 끝나고 난 후에 실행되는 것이기 때문에 필요가 없습니다. 진짜 확실하게 window.onload가 끝난 후에 실행시키고 있으면 document.readyState 프로퍼티를 사용해서 체크할수 있습니다.
- document_start, document_end, document_idle 일 수 있습니다. 기본값으로 document_idle를 가집니다.
주의사항! ContentScript가 못하는것!
아래를 제외한 크롬 api사용하기.사용할수 있는 것들.
- extension
- getURL
- inIncognitoContext
- lastError
- onRequest
- i18n
- runtime
- connect
- getManifest
- getURL
- id
- onConnect
- onMessage
- sendMessage
- storage
익스텐션의 페이지에 정의된 변수나 함수 사용하기.
웹 페이지나 다른 content scripts에 있는 변수나 함수 사용하기.
그 외 정보들을 쓰고 싶으면 message로 받아서 써야한다. message함수에 대해서는 뒤에서 다룬다.
contentScript 삽입 타이밍
- 일부 시기에만 스크립트를 삽입하고 싶으면 Programmatic injection에 기술된 permissions 필드를 이용합니다.
- content script가 페이지에 항상 삽입되어 있어야 한다면 익스텐션의 manifest파일에 아래처럼 등록하면 됩니다.
단축키 설정하기
# 단축키 설정하기
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+F",
"mac": "MacCtrl+Shift+F"
},
"description": "Opens hello.html"
}
}
Message 전달하고 전달받기
// 보내기
chrome.tabs.sendMessage(tabs[0].id, {"coldstart": "true"}, (response)=>{
})
// 받기
chrome.runtime.onMessage.addListener((msg, sender, sendResponse)=>{
})
메세지 보낼 때 주의사항
backgroundScript → contentScript 로 sendMessage 싶은데 동작을 안하고 계속 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. 에러가 발생했다.
document에 이유가 있었는데… contentScript로 보내고 싶으면 **tabs.sendMessage 를 써야한다고 한다!!**
tabs.sendMessage()
- backgroundScript → contentScript
- 다른 privileged script → contentScript
runtime.onMessage
- backgroundScript → contentScript
- backgroundScript ← contentScript
React+ Ts로 빌드하기
'Web' 카테고리의 다른 글
Code-Server서버에 https 적용하기 (1) | 2023.01.28 |
---|---|
[ChromeExtensionDev] "Refused to connect to '{URL}' because it violates the document's Content Security Policy." 해결하기 (2) | 2022.06.03 |
[영상리뷰] REST API를 버리고 Graph QL을 선택한 이유? (0) | 2020.01.07 |
언제 POST, 언제 GET을 써야 할 까? 헷깔릴 때 (1) | 2019.11.20 |
[영상리뷰]AWS를 쓸까? Heroku를 쓸까? 그 선택의 기준은? (0) | 2019.10.22 |