서비스 워커를 사용하여 페이지에 업데이트 브로드캐스트

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

일부 시나리오에서 서비스 워커는 특정 이벤트를 알리기 위해 서비스 워커가 제어하는 활성 탭과 사전에 통신해야 할 수 있습니다. 예를 들면 다음과 같습니다.

  • 새 버전의 서비스 워커가 설치되면 페이지에 알림을 전송하여 페이지에서 사용자에게 '업데이트하여 새로고침' 버튼을 표시하여 즉시 새 기능에 액세스할 수 있도록 합니다.
  • '이제 앱을 오프라인에서 사용할 수 있습니다' 또는 '새 버전의 콘텐츠를 사용할 수 있습니다'와 같은 표시를 통해 서비스 워커 측에서 발생한 캐시된 데이터의 변경사항을 사용자에게 알립니다.
업데이트를 전송하기 위해 페이지와 통신하는 서비스 워커를 보여주는 다이어그램

서비스 워커가 통신을 시작하기 위해 페이지에서 메시지를 수신할 필요가 없는 이러한 유형의 사용 사례를 '브로드캐스트 업데이트'라고 합니다. 이 가이드에서는 표준 브라우저 API와 Workbox 라이브러리를 사용하여 페이지와 서비스 워커 간에 이러한 유형의 통신을 구현하는 다양한 방법을 검토합니다.

프로덕션 케이스

Tinder

Tinder PWA는 workbox-window를 사용하여 페이지에서 중요한 서비스 워커 수명 주기 순간('설치됨', '제어됨', '활성화됨')을 리슨합니다. 이렇게 하면 새 서비스 워커가 실행될 때 '업데이트 가능' 배너가 표시되므로 사용자가 PWA를 새로고침하고 최신 기능에 액세스할 수 있습니다.

Tinder 웹 앱의 '업데이트 가능' 기능 스크린샷
Tinder PWA에서 서비스 워커는 페이지에 새 버전이 준비되었다고 알리고 페이지는 사용자에게 '업데이트 가능' 배너를 표시합니다.

Squoosh

Squoosh PWA에서 서비스 워커가 오프라인에서 작동하는 데 필요한 모든 애셋을 캐시하면 페이지에 메시지를 전송하여 '오프라인에서 작동할 준비가 되었습니다' 토스트를 표시하여 사용자에게 이 기능을 알립니다.

Squoosh 웹 앱의 '오프라인에서 작업할 준비가 되었습니다' 기능의 스크린샷
Squoosh PWA에서 서비스 워커는 캐시가 준비되면 페이지 업데이트를 브로드캐스트하고 페이지에 '오프라인에서 작업할 준비가 되었습니다' 토스트가 표시됩니다.

Workbox 사용

서비스 워커 수명 주기 이벤트 수신 대기

workbox-window중요한 서비스 워커 수명 주기 이벤트를 수신 대기하는 간단한 인터페이스를 제공합니다. 내부적으로 라이브러리는 updatefoundstatechange와 같은 클라이언트 측 API를 사용하고 workbox-window 객체에 상위 수준 이벤트 리스너를 제공하므로 사용자가 이러한 이벤트를 더 쉽게 사용할 수 있습니다.

다음 페이지 코드를 사용하면 새 버전의 서비스 워커가 설치될 때마다 이를 감지하여 사용자에게 전달할 수 있습니다.

const wb = new Workbox('/sw.js');

wb.addEventListener('installed', (event) => {
  if (event.isUpdate) {
    // Show "Update App" banner
  }
});

wb.register();

페이지에 캐시 데이터 변경사항 알림

Workbox 패키지 workbox-broadcast-update는 캐시된 응답이 업데이트되었음을 창 클라이언트에 알리는 표준 방법을 제공합니다. 이는 StaleWhileRevalidate 전략과 함께 가장 일반적으로 사용됩니다.

업데이트를 브로드캐스트하려면 서비스 워커 측의 전략 옵션에 broadcastUpdate.BroadcastUpdatePlugin를 추가합니다.

import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';

registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new StaleWhileRevalidate({
    plugins: [
      new BroadcastUpdatePlugin(),
    ],
  })
);

웹 앱에서는 다음과 같이 이러한 이벤트를 수신 대기할 수 있습니다.

navigator.serviceWorker.addEventListener('message', async (event) => {
  // Optional: ensure the message came from workbox-broadcast-update
  if (event.data.meta === 'workbox-broadcast-update') {
    const {cacheName, updatedUrl} = event.data.payload;

    // Do something with cacheName and updatedUrl.
    // For example, get the cached content and update
    // the content on the page.
    const cache = await caches.open(cacheName);
    const updatedResponse = await cache.match(updatedUrl);
    const updatedText = await updatedResponse.text();
  }
});

브라우저 API 사용

Workbox에서 제공하는 기능이 필요에 충분하지 않은 경우 다음 브라우저 API를 사용하여 '브로드캐스트 업데이트'를 구현하세요.

Broadcast Channel API

서비스 워커는 BroadcastChannel 객체를 만들고 여기에 메시지를 전송하기 시작합니다. 이러한 메시지를 수신하는 데 관심이 있는 모든 컨텍스트(예: 페이지)는 BroadcastChannel 객체를 인스턴스화하고 메시지 핸들러를 구현하여 메시지를 수신할 수 있습니다.

새 서비스 워커가 설치될 때 페이지에 알리려면 다음 코드를 사용하세요.

// Create Broadcast Channel to send messages to the page
const broadcast = new BroadcastChannel('sw-update-channel');

self.addEventListener('install', function (event) {
  // Inform the page every time a new service worker is installed
  broadcast.postMessage({type: 'CRITICAL_SW_UPDATE'});
});

페이지는 sw-update-channel를 구독하여 이러한 이벤트를 수신 대기합니다.

// Create Broadcast Channel and listen to messages sent to it
const broadcast = new BroadcastChannel('sw-update-channel');

broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'CRITICAL_SW_UPDATE') {
    // Show "update to refresh" banner to the user.
  }
};

간단한 기법이지만 브라우저 지원이 제한적입니다. 이 글을 작성하는 시점에서 Safari는 이 API를 지원하지 않습니다.

Client API

Client APIClient 객체 배열을 반복하여 서비스 워커에서 여러 클라이언트와 통신하는 간단한 방법을 제공합니다.

다음 서비스 워커 코드를 사용하여 포커스가 설정된 마지막 탭에 메시지를 보냅니다.

// Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    // Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});

페이지는 다음 메시지를 가로채는 메시지 핸들러를 구현합니다.

// Listen to messages
navigator.serviceWorker.onmessage = (event) => {
     if (event.data && event.data.type === 'MSG_ID') {
         // Process response
   }
};

Client API는 여러 활성 탭에 정보를 브로드캐스팅하는 등의 경우에 유용한 옵션입니다. API는 모든 주요 브라우저에서 지원되지만, 모든 메서드가 지원되는 것은 아닙니다. 사용하기 전에 브라우저 지원을 확인하세요.

메시지 채널

메시지 채널에는 페이지에서 서비스 워커로 포트를 전달하여 두 채널 간의 통신 채널을 설정하는 초기 구성 단계가 필요합니다. 페이지는 MessageChannel 객체를 인스턴스화하고 postMessage() 인터페이스를 통해 포트를 서비스 워커에 전달합니다.

const messageChannel = new MessageChannel();

// Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

페이지는 해당 포트에서 'onmessage' 핸들러를 구현하여 메시지를 리슨합니다.

// Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};

서비스 워커는 포트를 수신하고 포트 참조를 저장합니다.

// Initialize
let communicationPort;

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

이 시점부터 포트 참조에서 postMessage()를 호출하여 페이지에 메시지를 보낼 수 있습니다.

// Communicate
communicationPort.postMessage({type: 'MSG_ID' });

MessageChannel는 포트를 초기화해야 하므로 구현하기가 더 복잡할 수 있지만 모든 주요 브라우저에서 지원됩니다.

다음 단계

이 가이드에서는 Window to service worker 통신의 한 가지 특정 사례인 '브로드캐스트 업데이트'를 살펴봤습니다. 예를 들어 중요한 서비스 워커 수명 주기 이벤트를 수신 대기하고 콘텐츠 또는 캐시된 데이터의 변경사항에 대해 페이지에 통신하는 방법을 예로 들 수 있습니다. 서비스 워커가 이전에 메시지를 받지 않고 사전에 페이지와 통신하는 더 흥미로운 사용 사례를 생각해 볼 수 있습니다.

Window 및 서비스 워커 통신의 더 많은 패턴은 다음을 참고하세요.

  • 명령형 캐싱 가이드: 페이지에서 서비스 워커를 호출하여 리소스를 미리 캐시합니다(예: 미리 가져오기 시나리오).
  • 양방향 통신: 서비스 워커에 작업(예: 대용량 다운로드)을 위임하고 페이지에 진행 상황을 계속 알립니다.

추가 리소스