서비스 워커로 이벤트 처리

확장 프로그램 서비스 워커 개념을 다루는 튜토리얼

개요

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

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

시작하기 전에

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

확장 프로그램 빌드

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

1단계: 서비스 워커 등록

프로젝트의 루트에 manifest 파일을 생성하고 다음 코드를 추가합니다.

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를 사용하지만 먼저 매니페스트에서 omnibox 키워드 트리거를 선언해야 합니다.

manifest.json:

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

이제 스크립트 최상위 수준에서 omnibox 이벤트 리스너를 등록합니다. 사용자가 주소 표시줄에 검색주소창 키워드 (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() 함수는 omnibox 입력을 가져와 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"],
  "permissions": ["storage", "alarms"],
  "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;
  }
});

작동 여부 테스트

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

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

로컬에서 확장 프로그램 로드

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

참조 페이지 열기

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

예를 들면 다음과 같습니다.

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

오늘의 팁 열기

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

일일 도움말 열기
오늘의 도움말을 여는 빠른 API 확장 프로그램

🎯 잠재적 개선사항

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

  • omnibox 추천을 구현하는 다른 방법을 살펴보세요.
  • 확장 프로그램 도움말을 표시할 맞춤 모달을 만듭니다.
  • MDN의 웹 확장 프로그램 참조 API 페이지에 대한 추가 페이지를 엽니다.

계속 빌드하세요!

이 튜토리얼을 완료하신 것을 축하합니다 🎉. 다른 초보자 튜토리얼을 완료하여 실력을 계속 키워 보세요.

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

계속 탐색하기

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