서비스 워커로 이벤트 처리

확장 프로그램 서비스 워커 개념을 설명하는 튜토리얼입니다.

개요

이 튜토리얼에서는 Chrome 확장 프로그램 서비스 워커를 소개합니다. 이 튜토리얼의 일부로 사용자가 검색주소창을 사용하여 Chrome API 참조 페이지로 빠르게 이동할 수 있는 확장 프로그램을 빌드합니다. 다음을 수행하는 방법을 배우게 됩니다.

  • 서비스 워커를 등록하고 모듈을 가져옵니다.
  • 확장 프로그램 서비스 워커를 디버그합니다.
  • 상태 관리 및 이벤트 처리
  • 주기적 이벤트를 트리거합니다.
  • 콘텐츠 스크립트와 통신합니다.

시작하기 전에

이 가이드는 기본적인 웹 개발 경험이 있다고 가정합니다. 확장 프로그램 개발에 관한 소개는 확장 프로그램 101Hello World를 검토하는 것이 좋습니다.

확장 프로그램 빌드

먼저 quick-api-reference라는 새 디렉터리를 만들어 확장 프로그램 파일을 저장하거나 GitHub 샘플 저장소에서 소스 코드를 다운로드합니다.

1단계: 서비스 워커 등록

프로젝트의 루트에 매니페스트 파일을 만들고 다음 코드를 추가합니다.

manifest.json:

{
  "manifest_version": 3,
  "name": "Open extension API reference",
  "version": "1.0.0",
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "service-worker.js",
  },
}

확장 프로그램은 매니페스트에 서비스 워커를 등록하며, 매니페스트에는 단일 JavaScript 파일만 사용됩니다. 웹페이지에서와 마찬가지로 navigator.serviceWorker.register()를 호출할 필요가 없습니다.

images 폴더를 만든 다음 그 안에 아이콘을 다운로드합니다.

읽기 시간 튜토리얼의 첫 번째 단계를 확인하여 매니페스트에 있는 확장 프로그램의 메타데이터아이콘에 대해 자세히 알아보세요.

2단계: 여러 서비스 워커 모듈 가져오기

서비스 워커는 두 가지 기능을 구현합니다. 유지관리를 개선하기 위해 각 기능을 별도의 모듈에 구현할 예정입니다. 먼저 매니페스트에서 서비스 워커를 ES 모듈로 선언해야 합니다. 이렇게 하면 서비스 워커에서 모듈을 가져올 수 있습니다.

manifest.json:

{
 "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
}

service-worker.js 파일을 만들고 두 개의 모듈을 가져옵니다.

import './sw-omnibox.js';
import './sw-tips.js';

이러한 파일을 만들고 각 파일에 콘솔 로그를 추가합니다.

sw-omnibox.js:

console.log("sw-omnibox.js")

sw-tips.js:

console.log("sw-tips.js")

서비스 워커에서 여러 파일을 가져오는 다른 방법에 대한 자세한 내용은 스크립트 가져오기를 참조하세요.

선택사항: 서비스 워커 디버깅

서비스 워커 로그를 찾는 방법과 로그가 언제 종료되었는지 알 수 있는 방법을 설명하겠습니다. 먼저 안내에 따라 압축해제된 확장 프로그램을 로드합니다.

30초 후에 서비스 워커가 종료되었음을 의미하는 '서비스 워커 (비활성)'가 표시됩니다. 'service worker (inactive)' 링크를 클릭하여 검사합니다. 다음 애니메이션은 이를 보여줍니다.

서비스 워커를 검사하다가 잠에서 깨셨습니까? devtools에서 서비스 워커를 열면 활성 상태로 유지됩니다. 서비스 워커가 종료될 때 확장 프로그램이 올바르게 동작하도록 하려면 DevTools를 닫아야 합니다.

이제 확장 프로그램을 중단하여 오류가 있는 위치를 알아봅니다. 이렇게 하는 한 가지 방법은 service-worker.js 파일의 './sw-omnibox.js' 가져오기에서 '.js'를 삭제하는 것입니다. Chrome에서 서비스 워커를 등록할 수 없습니다.

chrome://extensions로 돌아가 확장 프로그램을 새로고침합니다. 다음 두 가지 오류가 표시됩니다.

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

확장 프로그램 서비스 워커를 디버그하는 다양한 방법은 확장 프로그램 디버깅을 참고하세요.

4단계: 상태 초기화

서비스 워커가 필요하지 않은 경우 Chrome에서 서비스 워커를 종료합니다. chrome.storage API를 사용하여 서비스 워커 세션 간에 상태를 유지합니다. 저장소 액세스를 위해서는 매니페스트에서 권한을 요청해야 합니다.

manifest.json:

{
  ...
  "permissions": ["storage"],
}

먼저 기본 추천을 저장소에 저장합니다. 확장 프로그램이 처음 설치될 때 runtime.onInstalled() 이벤트를 수신하여 상태를 초기화할 수 있습니다.

sw-omnibox.js:

...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === 'install') {
    chrome.storage.local.set({
      apiSuggestions: ['tabs', 'storage', 'scripting']
    });
  }
});

서비스 워커는 창 객체에 직접 액세스할 수 없으므로 window.localStorage를 사용하여 값을 저장할 수 없습니다. 또한 서비스 워커는 단기 실행 환경입니다. 사용자의 브라우저 세션 내내 반복적으로 종료되므로 전역 변수와 호환되지 않습니다. 대신 로컬 머신에 데이터를 저장하는 chrome.storage.local를 사용하세요.

확장 프로그램 서비스 워커의 다른 스토리지 옵션에 대한 자세한 내용은 전역 변수를 사용하는 대신 데이터 유지를 참조하세요.

5단계: 이벤트 등록

모든 이벤트 리스너는 서비스 워커의 전역 범위에 정적으로 등록되어야 합니다. 즉, 이벤트 리스너는 비동기 함수에 중첩되어서는 안 됩니다. 이렇게 하면 서비스 워커가 재부팅되는 경우 Chrome에서 모든 이벤트 핸들러가 복원되도록 할 수 있습니다.

이 예에서는 chrome.omnibox API를 사용하지만 먼저 매니페스트에서 검색주소창 키워드 트리거를 선언해야 합니다.

manifest.json:

{
  ...
  "minimum_chrome_version": "102",
  "omnibox": {
    "keyword": "api"
  },
}

이제 스크립트의 최상위 수준에서 검색주소창 이벤트 리스너를 등록합니다. 사용자가 주소 표시줄에 검색주소창 키워드 (api)와 탭 또는 공백을 차례로 입력하면 Chrome은 저장용량에 있는 키워드를 기반으로 추천 목록을 표시합니다. 현재 사용자 입력과 suggestResult 객체를 사용하는 onInputChanged() 이벤트는 이러한 추천을 채웁니다.

sw-omnibox.js:

...
const URL_CHROME_EXTENSIONS_DOC =
  'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;

// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
  await chrome.omnibox.setDefaultSuggestion({
    description: 'Enter a Chrome API or choose from past searches'
  });
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  const suggestions = apiSuggestions.map((api) => {
    return { content: api, description: `Open chrome.${api} API` };
  });
  suggest(suggestions);
});

사용자가 추천 검색어를 선택하면 onInputEntered()에서 해당하는 Chrome API 참조 페이지를 엽니다.

sw-omnibox.js:

...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
  chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
  // Save the latest keyword
  updateHistory(input);
});

updateHistory() 함수는 검색주소창 입력을 받아 storage.local에 저장합니다. 이렇게 하면 가장 최근 검색어를 나중에 검색주소창 추천으로 사용할 수 있습니다.

sw-omnibox.js:

...
async function updateHistory(input) {
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  apiSuggestions.unshift(input);
  apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
  return chrome.storage.local.set({ apiSuggestions });
}

6단계: 반복 일정 설정하기

setTimeout() 또는 setInterval() 메서드는 일반적으로 지연되거나 주기적인 작업을 실행하는 데 사용됩니다. 그러나 이러한 API는 서비스 워커가 종료될 때 스케줄러가 타이머를 취소하기 때문에 실패할 수 있습니다. 대신 확장 프로그램은 chrome.alarms API를 사용할 수 있습니다.

먼저 매니페스트에서 "alarms" 권한을 요청합니다. 또한 원격 호스팅 위치에서 확장 프로그램 도움말을 가져오려면 호스트 권한을 요청해야 합니다.

manifest.json:

{
  ...
  "permissions": ["storage", "alarms"],
  "permissions": ["storage"],
  "host_permissions": ["https://extension-tips.glitch.me/*"],
}

확장 프로그램은 모든 도움말을 가져온 후 무작위로 하나를 선택하여 저장소에 저장합니다. 팁을 업데이트하기 위해 하루에 한 번 실행되는 알람을 생성합니다. Chrome을 종료하면 알람이 저장되지 않습니다. 따라서 알람이 있는지 확인하고 없으면 새로 만들어야 합니다.

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://extension-tips.glitch.me/tips.json');
  const tips = await response.json();
  const randomIndex = Math.floor(Math.random() * tips.length);
  return chrome.storage.local.set({ tip: tips[randomIndex] });
};

const ALARM_NAME = 'tip';

// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
  const alarm = await chrome.alarms.get(ALARM_NAME);
  if (typeof alarm === 'undefined') {
    chrome.alarms.create(ALARM_NAME, {
      delayInMinutes: 1,
      periodInMinutes: 1440
    });
    updateTip();
  }
}

createAlarm();

// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);

7단계: 다른 컨텍스트와 통신

확장 프로그램은 콘텐츠 스크립트를 사용하여 페이지의 콘텐츠를 읽고 수정합니다. 사용자가 Chrome API 참조 페이지를 방문하면 확장 프로그램의 콘텐츠 스크립트가 해당 페이지를 최신 정보로 업데이트합니다. 서비스 워커에 하루의 팁을 요청하는 메시지를 전송합니다.

먼저 매니페스트에서 콘텐츠 스크립트를 선언하고 Chrome API 참조 문서에 해당하는 일치 패턴을 추가합니다.

manifest.json:

{
  ...
  "content_scripts": [
    {
      "matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
      "js": ["content.js"]
    }
  ]
}

새 콘텐츠 파일을 만듭니다. 다음 코드는 서비스 워커에 팁을 요청하는 메시지를 보냅니다. 그런 다음 확장 프로그램 팁이 포함된 팝오버를 여는 버튼을 추가합니다. 이 코드는 새로운 웹 플랫폼 Popover API를 사용합니다.

content.js:

(async () => {
  // Sends a message to the service worker and receives a tip in response
  const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });

  const nav = document.querySelector('.upper-tabs > nav');
  
  const tipWidget = createDomElement(`
    <button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
      <span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
    </button>
  `);

  const popover = createDomElement(
    `<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
  );

  document.body.append(popover);
  nav.append(tipWidget);
})();

function createDomElement(html) {
  const dom = new DOMParser().parseFromString(html, 'text/html');
  return dom.body.firstElementChild;
}

마지막 단계는 서비스 워커에 일일 팁이 포함된 답장을 콘텐츠 스크립트에 보내는 메시지 핸들러를 서비스 워커에 추가하는 것입니다.

sw-tips.js:

...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.greeting === 'tip') {
    chrome.storage.local.get('tip').then(sendResponse);
    return true;
  }
});

작동 테스트

프로젝트의 파일 구조가 다음과 같은지 확인합니다.

확장 프로그램 폴더의 콘텐츠: images folder, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js, content.js

로컬로 확장 프로그램 로드

개발자 모드에서 압축해제된 확장 프로그램을 로드하려면 Hello world의 단계를 따르세요.

참조 페이지 열기

  1. 브라우저 주소 표시줄에 키워드 'api'를 입력합니다.
  2. 'Tab' 또는 '스페이스바'를 누릅니다.
  3. API의 전체 이름을 입력하세요.
    • 또는 이전 검색어 목록에서 선택
  4. 새 페이지에 Chrome API 참조 페이지가 열립니다.

다음과 같이 표시됩니다.

런타임 API 참조를 여는 빠른 API 참조
Runtime API를 여는 빠른 API 확장 프로그램입니다.

오늘의 정보 열기

탐색 메뉴에 있는 도움말 버튼을 클릭하여 확장 프로그램 도움말을 엽니다.

일일 팁 열기
오늘의 정보를 소개하는 간단한 API 확장 프로그램

셰프 강화 가능

오늘 배운 내용을 바탕으로 다음 중 하나를 달성해 보세요.

  • 검색주소창 추천을 구현하는 다른 방법을 알아보세요.
  • 확장 프로그램 팁을 표시하는 맞춤 모달을 만듭니다.
  • MDN의 웹 확장 프로그램 참조 API 페이지에 대한 추가 페이지를 엽니다.

계속 커뮤니티를 키워나가세요.

이 튜토리얼을 완료하신 것을 축하합니다 🎉. 다른 초급 튜토리얼을 진행하여 실력을 계속 향상해 보세요.

확장 학습할 내용
읽기 시간 특정 페이지 집합에 요소를 자동으로 삽입
탭 관리자 브라우저 탭을 관리하는 팝업을 만듭니다.
집중 모드 확장 프로그램 작업을 클릭한 후 현재 페이지에서 코드를 실행합니다.

계속 둘러보기

확장 프로그램 서비스 워커 학습 과정을 계속하려면 다음 도움말을 살펴보는 것이 좋습니다.