Register

1
2
3
4
5
6
navigator.serviceWorker.register('your_service_worker,js', { scope: 'your_path_name' })
.then((worker) => {
console.log('[SW] Registration succeeded.')
}).catch((err) => {
console.log('[SW] Registration failed with ' + err)
})

Unregister

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
navigator.serviceWorker.getRegistration('your_service_worker.js', { scope: 'your_path_name' })
.then((worker) => {
if (worker && worker.unregister) {
worker.unregister()
.then((isUnregistered) => {
if (isUnregistered) {
console.log('[SW] Unregister succeeded')
} else {
console.log('[SW] Unregister failed')
}
})
}
})
.catch((err) => {
console.log('[SW] Unregister failed with ' + err)
})

Worker.js

1
2
3
4
5
6
7
8
9
10
11
12
self.addEventListener('install', (event) => {
console.log('[Worker] installing')
})

self.addEventListener('activate', (event) => {
console.log('[Worker] ready')
})

self.addEventListener('fetch', (event) => {
console.log(event.request.url)
return
})

LifeCycle

Download and Parse

After registration of Service Worker, browser will download and parse the script from specified url.

1
2
3
4
5
6
7
8
9
10
/* In main.js */
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
.then((worker) => {
console.log('Service Worker Registered')
})
.catch((err) => {
console.log('Service Worker Failed to Register')
})
}

Installing

After successfully parsed, browser will install the Service Worker. Once the Install finished, the worker will emit install event, and waiting for activating. If the install failed, Service Worker will be Redundant

1
2
3
4
5
6
7
8
9
/* In sw.js */
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(currentCacheName)
.then((cache) => {
return cache.addAll(arrayOfFilesToCache)
})
)
})

If there is an event.waitUntil(promiseObj) method in the event, the installing event will not be successful until the Promise within it is solved. If the Promise is rejected, the install event fails and the Service Worker became redundant

Installed / Waiting

If the installation is successful, the Service Worker moves to the installed(also called waiting) state. In this state it is a valid, but not yet active, worker. It is not yet in control of the document., but rather is waiting to take control form the current worker.

Activating

The activating state will be triggered for a waiting Service Worker in one of the following scenarios -

  • if there is no current active worker already

  • if the self.skipWaiting() method is called in the Service Worker script

  • if the user has navigated away from the page, thereby releasing the previous active worker.

  • if a specified period of time has passed, thereby releasing the previous active worker

During the activating state, the active event in the Service Worker script is carried out. In a typical activate event, we clear files from old caches.

1
2
3
4
5
6
7
8
9
10
11
12
13
/* In sw.js */
self.addEventListener('activate', (event) => {
event.waitUntil(
// get all the cache names
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.filter((cacheName) => {

})
)
})
)
})

Activate

If the worker is activated, it will emit active event, and take over all fetch from page. If active failed, the worker will be Redundant

Redundant

Once a worker is redundant, it won’t have effect on page.

Dependencies

  • fetch

  • promise

  • CacheStorage: store Cache Object

  • Cache: store Request/Response Pair Object

Example: Cache Static Resource

  • listen to install to cache static resource

  • listen to fetch to intercept request and return cached static Resource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var CACHE_NAME = 'my-site-cache-v1'
var urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js',
]

// cache resource after installation
self.addEventListener('install', (event) => {
// perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Opened Cache')
return cache.addAll(urlsToCache)
})
)
})

// listen to fetch, use cache if cached
self.addEventListener('fetch', (event) => {
event.responseWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response
}
return fetch(event.request)
})
)
})

Version Controls

1
2
3
4
5
6
7
8
9
10
11
var deleteObsoleteCache = () => {
return caches.keys().then((keys) => {
var all = keys.map((key) => {
if (key.indexOf(CACHE_PREFIX) !== -1 && key.indexOf(CACHE_VERSION) === -1) {
console.log('[SW] Delete cache: ' + key)
return caches.delete(key)
}
})
return Promise.all(all)
})
}

Whitelist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// resource whitelist
const allAssets = [
'//your.cdn.com/app.css',
'//your.cdn.com/app.js',
]

// match
const matchAssets = (requestUrl) => {
const urlObj = new URL(requestUrl)
const noProtocolUrl = urlObj.href.substr(urlObj.protocol.length)
if (allAssets.includes(noProtocolUrl)) {
return true
}
return false
}

// listen to fetch, proxy fetch matching Whitelist
self.addEventListener('fetch', (event) => {
try {
const requestUrl = event.target.url
const isGet = event.request.method === 'GET'
const assetMatches = matchAssets(requestUrl)
if (!assetMatches || !isGet) {
return
}
const resource = cacheFirstResponse(event)
event.responseWith(resource)
} cache (err) {
console.log('[SW] handle fetch event error')
return
}
})