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

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

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

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

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

프로덕션 케이스

Tinder

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

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

스쿼시

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는 캐시된 응답이 업데이트되었음을 창 클라이언트에 알리는 표준 방법을 제공합니다. 이는 StaleRestartRevalidate 전략과 함께 가장 일반적으로 사용됩니다.

업데이트를 브로드캐스트하려면 다음과 같이 서비스 워커 측의 전략 옵션에 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를 사용하여 '브로드캐스트 업데이트'를 구현합니다.

방송 채널 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 통신의 특정 사례인 'broadcast updates'를 살펴보았습니다. 여기서 다루는 예시에는 중요한 서비스 워커 수명 주기 이벤트 수신 대기, 콘텐츠 또는 캐시된 데이터의 변경사항에 관한 페이지 커뮤니케이션 등이 포함됩니다. 서비스 워커가 어떠한 메시지도 받지 않고 페이지와 사전에 커뮤니케이션하는 보다 흥미로운 사용 사례를 생각해 볼 수 있습니다.

Window 및 서비스 워커 통신의 추가 패턴은 다음을 확인하세요.

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

추가 리소스