-
Notifications
You must be signed in to change notification settings - Fork 0
Service Worker
서비스 워커는 브라우저가 백그라운드에서 실행하는 스크립트로, 웹 페이지와는 별개로 작동하며 웹 페이지 또는 사용자의 인터랙션이 필요하지 않은 기능만 제공하고 있다. 서비스 워커의 수명주기는 웹 페이지와는 완전히 별개이다. 웹 서비스와 브라우저 및 네트워크 사이에서 프록시 서버 역할을 하며, 오프라인에서도 서비스를 사용할 수 있도록 한다.
웹 페이지와 별개로 존재하기 때문에 다음과 같은 제약이 있다.
- 서비스 워커는 요청하지 않는 이상, 없는 것이나 다름이 없다.
- 웹 페이지 life cycle을 따르지 않는다. 서비스 워커는 웹 페이지가 닫히더라도 자동으로 비활성화되지 않는다.
- 웹 페이지와 별개로 존자해므로 DOM이나 window 요소에 접근할 수 없다.
fetch
이벤트의 중간자 역할로 사용할 수 있다. 이 경우 서비스 워커는 HTTP를 통해 정보를 요청하는 대신 가지고 있는 캐시에서 자료를 전달한다. 캐시가 삭제되지 않는 한 브라우저는 인터넷 연결 없이도 정보를 보여줄 수 있다.
브라우저 창이 닫힌 상태에서도 동작하므로, 푸시 알림을 구현할 수 있다.
채팅 메시지 또는 사진 업로드 등의 작업 도중 컴퓨터가 오프라인 상태가 되는 경우 온라인 상태가 되었을 때 해당 작업을 마저 완료할 수 있다.
fetch란 web resource에 접근하기 위해 행해지는 모든 request action을 의미한다. service worker는 fetch를 통해 발생하는 모든 http request를 중간에서 가로챌 수 있다. (proxy) 예를 들어 실 서버로 보내지는 request를 중간에서 가로채어 서버로 보내지 말고 대신 cache 된 데이터를 제공하는 등의 처리를 하는 것이 가능하다.
http request가 있을 때마다 fetch이벤트가 발생하는데 service worker는 fetch event listener를 등록하여 이벤트를 감지하고 있다가 fetch이벤트가 발생할 때 listener에서 request를 intercept하는 방식이다.
- 윈도우에서 해당 윈도우를 제어하는 서비스 워커로 메시지 보내기
- 서비스 워커에서 범위 내의 모든 윈도우로 메시지 보내기
- 서비스 워커에서 특정 윈도우로 메시지 보내기
- 윈도우간 메시지 보내기 (서비스 워커를 통해)
페이지에서 서비스워커를 가져오려면 다음과 같이한다.
navigator.serviceWorker.controller
그 후에는 메시지 자체를 첫번째 인수로 받는 서비스 워커의 postMessage()
메서드를 활용한다.이 메시지는 무엇이든 될 수 있다.
메시지가 게시되고 나면 서비스 워커는 message 이벤트를 수신하여 해당 메시지를 받을 수 있다.
self.addEventListener('message', function(event){
console.log(event.data)
})
위 예제는 메시지를 받아서 콘솔에 출력한다. 메시지에 포함된 콘텐츠는 이벤트 리스너에 전달된 이벤트 객체의 데이터 속성에서 찾을 수 있다. 메시지 데이터 자체를 포함하는 것 이외에도 이벤트 객체는 여러 유용한 속성을 가지고 있다. 가장 유용한 속성중 일부는 source 속성에 있다.
source 속성에는 메시지를 보낸 윈도우 정보가 들어있다. 이 정보를 활용해서 무엇을 할지 그리고 응답을 어디로 보낼지 판단할 수 있다. 다음 코드는 메시지의 source 속성을 사용하는 간단한 예제 코드이다.
self.addEventListener('message', function(event) {
console.log('Message received:', event.data);
console.log('From a window with the id:', event.source.id);
console.log('which is currently pointing at:', event.source.url);
console.log('and is', event.source.focused ? 'focused' : 'not focused');
console.log('and', event.source.visibilityState)
})
페이지에서 서비스워커로 해당 페이지를 캐시하라고 요청을 보낸다.
navigator.serviceWorker.controller.postMessage('cache-current-page')
사용자가 해당 페이지에 방문하면 서비스 워커에 메시지를 보냅니다. 서비스 워커는 이 메시지를 수신하고, 어느 페이지를 캐싱할지 결정하기 위해 이벤트 source 속성의 값을 사용할 수 있다.
서비스 워커에서 페이지로 메시지를 보내는 것은 페이지에서 서비스 워커로 메시지를 게시하는 것과 비슷하다. 유일한 차이점은 postMessage()를 호출하는 객체이다. 지금까지 서비스 워커에서 postMessage()를 호출했다면 이번에는 서비스 워커 클라이언트에서 호출한다.
서비스 워커내에서 서비스 워커의 글로벌 객체인 clients 객체를 사용해 현재 서비스 워커 범주안에 열려 있는 모든 윈도우(Window Clients)를 가져올 수 있다. clients 객체는 matchAll 메서드를 가지고 있다. 이 메서드는 서비스워커 범주 내에 열려 있는 모든 윈도우를 가져오는데 사용된다. matchAll() 메서드는 0개 혹은 그 이상의 WindowClient 객체를 담는 배열로 리졸브되는 프로미스를 반환한다.
self.clients.matchAll().then(function(clients) {
clients.forEach(function(client) {
if (client.url.includes("/my-account")) {
client.postMessage("Hi client: " + client.id;
}
})
})
이 코드는 현재 서비스 워커가 제어하는 모든 클라이언트를 가져와 하나씩 돌며 페이지를 표시하고 있는 윈도우로 메시지를 보낸다. 이 메시지를 윈도우에서 받는 코드는 아래와 같다.
navigator.serviceWorker.addEventListener('message', function(event){
console.log(event.data)
})
서비스 워커가 설치되고 필요한 모든 리소스 캐싱이 끝나면 앱 사용자에게 온라인이든 오프라인이든 상관없이 앱을 사용할 수 있다고 알려야합니다.
self.addEventListener('install', function() {
event.waitUntil (
caches.open(CACHE_NAME).then(function(cache) {
return cache.addAll(CACHED_URLS)
}).then(function() {
return self.clients.matchAll({
includeUncontrolled: true
})
}).then(function (clients) {
clients.forEach(client) {
client.postMessage('caching-complete')
}
})
)
})
위 코드에서 clients.matchAll()를 호출할 때 옵션 객체를 전달하여 제어되지 않는 클라이언트도 결과에 포함되도록 요청한다.
사용자가 페이지에 처음 방문하면 서비스 워커가 설치되고 활성화됩니다. 하지만 페이지는 여전히 서비스 워커에의해 제어되지 않습니다. 만일 self.clients.matchAll()에 제어되지 않는 윈도우를 포함하지 않도록 알려주지 않았다면 메시지는 목적지에 도달하지 못할 것 입니다.
서비스 워커의 라이프사이클은 복잡하다.
서비스 워커가 무엇을 하려는지, 그리고 어떤 그 라이프사이클이 어떤 이점이 있는지 모른다면, 서비스워커와 싸우고 있는 것처럼 느껴질 수 있다.
But once you know how it works, you can deliver seamless, unobtrusive updates to users, mixing the best of web and native patterns.
서비스 워커 라이프 사이클의 목적은 다음과 같다.
- Make offline-first possible.
- Allow a new service worker to get itself ready without disrupting the current one.
- Ensure an in-scope page is controlled by the same service worker (or no service worker) throughout.
- Ensure there's only one version of your site running at once.
라이프 사이클의 기본에 대해서 알아보자.
- The install event is the first event a service worker gets, and it only happens once.
- A promise passed to installEvent.waitUntil() signals the duration and success or failure of your install.
- A service worker won't receive events like fetch and push until it successfully finishes installing and becomes "active".
- By default, a page's fetches won't go through a service worker unless the page request itself went through a service worker. So you'll need to refresh the page to see the effects of the service worker.
- clients.claim() can override this default, and take control of non-controlled pages.
The first event a service worker gets is install. It's triggered as soon as the worker executes, and it's only called once per service worker. If you alter your service worker script the browser considers it a different service worker, and it'll get its own install event.
The install event is your chance to cache everything you need before being able to control clients. The promise you pass to event.waitUntil() lets the browser know when your install completes, and if it was successful.
If your promise rejects, this signals the install failed, and the browser throws the service worker away. It'll never control clients.
Once your service worker is ready to control clients and handle functional events like push and sync, you'll get an activate event.
But that doesn't mean the page that called .register() will be controlled.
You can take control of uncontrolled clients by calling clients.claim() within your service worker once it's activated.
If you use your service worker to load pages differently than they'd load via the network, clients.claim() can be troublesome, as your service worker ends up controlling some clients that loaded without it.
Note: I see a lot of people including clients.claim() as boilerplate, but I rarely do so myself. It only really matters on the very first load, and due to progressive enhancement the page is usually working happily without service worker anyway.