서비스 워커는 오프라인 지원이나 웹 푸시를 가능하게 하는 좋은 기능이다. 하지만 그 작동 방식과 브라우저의 지원 문제로 꽤 문제가 있다. 특히 주로 겪는 문제는 이전 서비스 워커를 업데이트하는 과정에서일 것이다. 서비스 워커는 클라이언트 단위로 작동한다. 따라서 여러 탭에 같은 사이트를 켜놓고 있으면 각각의 서비스 워커가 따로 작동하는 문제가 발생한다.

이를 동기화하기 위해서 개발자는 2가지의 방식을 취한다. 하나는 새로운 서비스 워커가 있으면 바로 반영하게 하는 것과 다른 하나는 사용자에게 푸시 메시지를 보내서 업데이트할 수 있도록 하는 것이다. 하지만 전자의 경우 사용자가 폼 작성 중에 서비스 워커가 업데이트되면 사용자의 의사와는 관계없이 자동으로 리프레시 하는 문제가 있다.1

후자도 문제가 만만치 않다. 예상되는 케이스는 2가지일 것이다. 하나는 만일 서비스 워커를 업데이트하라는 푸시 메시지를 못 보고 사이트 내 다른 곳을 클릭하는 경우와 다른 하나는 푸시 메시지를 보고 브라우저의 리프레시 버튼을 누르는 경우이다.

두 가지는 서로 나눠 생각해봐야 한다. 먼저 첫 번째의 경우는 서비스 워커의 상태가 installing 전 waiting이면 다시금 푸시 메시지를 뜨게 하는 방식으로 해결할 수 있다. 하지만 두번째의 경우는 조금 접근이 달라진다. 브라우저 리프레시를 가능하게 하더라도 만일 사이트가 2개 이상 켜져있는 경우 다른 탭의 작업 내용을 무시하고 리프레시 되는 문제가 발생한다.

따라서 서비스 워커를 제대로 적용하기 위해서는 다음처럼 되어야 한다.

  1. 새로운 서비스 워커가 있으면 사용자에게 푸시 메시지 보내기
  2. 사이트가 하나만 켜져있으면 브라우저 리프레시를 허용하고 둘 이상이면 1번 다시 내보내기

1번은 워커의 postMessage로 skipWaiting 보내는 방식으로 해결할 수 있다. 2번은 어떻게 해결할 수 있을까 고민이었는데 다행히 같은 생각을 한 사람이 있었다.2 Client API를 이용해서 클라이언트 수를 체크한 뒤 skipWaiting 후 HTTP Refresh 헤더를 보내는 방식이다. 다만, 파이어폭스의 경우 서비스 워커의 registration.waiting이 항상 null값이라서3 skipWaiting이 불가능하기 때문에 1번의 방식으로 작동한다.

event.respondWith((async () => {
if (event.request.mode === "navigate"
    && event.request.method === "GET"
    && registration.waiting
    && (await clients.matchAll()).length < 2
) {
    registration.waiting.postMessage('skipWaiting')
    return new Response("", {headers: {"Refresh": "0"}})
}
return await caches.match(event.request) ||
    fetch(event.request)
})())

현재 사이트에도 다음과 같은 접근 방식을 취해서 코드를 새로 짰다.

  1. 파이어폭스의 경우 자동으로 서비스 워커의 업데이트를 가져온다. 크롬이나 사파리는 사용자가 사이트를 새로 접속하거나 새로고침 했을 때만 적용된다. 

  2. stackoverflow.com 

  3. bugzilla.mozilla.org