티스토리 뷰

부스트캠프 AI Tech 3기 추천시스템 최종 프로젝트를 진행하며 개발했습니다.

 

 이 글은 Chrome Extension을 만드는 방법에 대해서 소개하고, 주의해야 할 사항을 설명하는 포스팅입니다. 

 

내가 만든 추천시스템! github.com/레포 일 때 레포별로 연관 레포지토리를 추천해준다.

 

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 프로퍼티를 사용해서 체크할수 있습니다.

주의사항! ContentScript가 못하는것!

아래를 제외한 크롬 api사용하기.사용할수 있는 것들.

  1. extension
    1. getURL
    2. inIncognitoContext
    3. lastError
    4. onRequest
  2. i18n
  3. runtime
    1. connect
    2. getManifest
    3. getURL
    4. id
    5. onConnect
    6. onMessage
    7. sendMessage
  4. 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로 빌드하기

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
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
글 보관함