title: Service Worker author: name: I'm Adrien, frontend developer url: https://oodrive.com email: a.gibrat@oodrive.com theme: ./theme controls: false output: index.html
--
<script type="presenter/text"> I work at Oodrive an international software editor We create B2B products to securly share, backup & sign documents in the cloud Our headquarters are in Paris, France We are also here in Iasi, with a great team of developers called the "Iron Team" (backend, frontend and QA) Thank you to have invited me and my french accent ;) Please, bear with me because it's my first talk with such a large audience and also my very first talk in english Yes, I do like challenge </script>--
<script type="presenter/text"> Today I want to make you discover Service worker because it push the web plateform forwards & allows to do pretty cool stuffs! It's getting more traction every day and it will be available *very soon* in all evergreen browsers You should have heard about Service Worker If not, you may have heard about Progressive Web Apps: It's a web page that try to behave like a native application Service worker is the core technology that enable this kind of magic ;) </script>--
<script type="presenter/text"> But first, lets start with the basics </script>--
sequenceDiagram
participant B as π Browser
participant X
B->X: HTTP requests, compute, DOM changes ...
--
sequenceDiagram
participant B as π Browser
participant W as π Worker
B->>W: create
--
sequenceDiagram
participant B as π Browser
participant W as π Worker
B->>W: create
loop events
W--xB: message
B--xW:
end
--
All run background scripts independently of any UI
- Web Workers since 2010, perform heavy computation, ex: pdfjs
- Shared Workers since 2010, shared across same origin
webkit in 2015 - Service Workers since 2014, control HTTP requests from same origin
--
<script type="presenter/text"> Ok, enough teasing ;) </script>--
A service worker acts as a proxy between the browser (your web application) and the network
Main usages
- Offline access
graph LR
B("π") --> S("π")
S -.- N("β")
style B fill:none,stroke:none
style S fill:none,stroke:none
style N fill:none,stroke:none
- Push notifications
graph RL
N("π¨") --> S("π")
S -.- B("π")
style B fill:none,stroke:none
style S fill:none,stroke:none
style N fill:none,stroke:none
--
support: chrome, mozilla, android, opera, edge, safari
- Promises construction to execute code after a task is fulfilled
- postMessage method to communicate between contexts
- fetch function to make simple HTTP requests, successor of XHR
--
support: chrome, mozilla, android, opera, edge (development), safari (development)
- HTTPS only, same origin for security reason
- No DOM access no
document
, norwindow
- Behind flag in Edge & Safari Technical Preview
--
<script type="presenter/text"> Yeah, I know you'd like to see some code... </script>--
support: chrome, mozilla, android, opera, edge (development), safari (development)
main.js
if (navigator.serviceWorker)
navigator.serviceWorker
.register('/service-worker.js', { scope: '/' })
.then(registration => registration.state)
Multiple Service Workers must register distinct scopes
<script type="presenter/text"> The first thing you need to do is to register your service worker Provide a script URL, and optional settings By default the scope is the path of the Service Worker script The scope is the path on your origin behind which the Service Worker will be active Like the path for a cookie -> Multiple Service Workers must register distinct scopes You may have a SW dedicated to assets with the '/public' scope And another one dedicated to data requests behind the '/api' scope In the end, your get a registration Object that gives you access to the state of the Service Worker amongst other things </script>--
support: chrome, mozilla, android, opera, edge (development), safari (development)
service-worker.js
self.addEventListener('install', event =>
event.waitUntil(/* ready to activate */)
)
graph LR
P["π page"] -. register .-> I("β install")
I --> A("β activate")
I --> E("β error")
style P fill:white
style I fill:white,stroke:#41b6e8
style A fill:white,stroke:green
style E fill:white,stroke:red
--
support: chrome, mozilla, android, opera, edge (development), safari (development)
- Activates only inside scope
- Skip the
install
event handler
graph LR
P["π page"] -. in scope .-> A("β activate")
A -. control .-> P
click P callback "in the Service Worker Scope"
style P fill:white,stroke:lightgrey
style A fill:white,stroke:green
π Service Worker: An Introduction
--
support: chrome, mozilla, android, opera, edge (development), safari (development)
New Service Worker will wait deactivation of the old one before it activates
graph LR
subgraph Old Service Worker
O("β activate") --> D("β deactivate")
end
subgraph New Service Worker
O("β activate") -. updatefound .-> I("β install")
I -. waiting .-> A("β activate")
end
style O fill:white,stroke:lightgrey
style D fill:white,stroke:red
style I fill:white,stroke:#41b6e8
style A fill:white,stroke:green
--
Main functional events
- fetch intercepted an HTTP request (made by main thread)
- message received a message via postMessage
- push received a push notification
--
graph LR
A("β Activated") --> D{"β Idle"}
D --> F("β Handle event")
F --> D
D --> T["β Terminated"]
T --> F
style A fill:white,stroke:green
style D fill:white
style F fill:white,stroke:#41b6e8
style T fill:white,stroke:red
Service worker may terminate at any time!
--
--
You cannot rely on global state within a service worker
- No Local Storage
- But IndexedDB (use a wrapper!)
- Use the Cache interface
--
- Beware of redirects, credentials, streams
- Avoid puting version in script URL
- Specific debug tools chrome://serviceworker-internals
- By default first run does not
control
the main Thread
--
--
support: chrome, mozilla, android, opera, edge (development), safari (development)
service-worker.js
const CACHE = 'my-awsome-pwa',
const FILES = ['/', '/styles.css', '/script.js']
self.addEventListener('install', event =>
event.waitUntil(
caches.open(CACHE)
.then(cache => cache.addAll(FILES))
)
)
π Service Worker specification
--
support: chrome, mozilla, android, opera, edge (development), safari (development)
service-worker.js
self.addEventListener('fetch', event =>
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
)
)
--
- Cache only KISS Progressive Web Application
- Cache first, falling back to network static assets
- Network first, falling back to cache API resources
- etc
--
--
- Used to be complex vendor specific implementations
- Fully standardized browser provides push server
- Native notifications UI with user permission
π¨ Push Notifications in Web App
--
support: chrome, mozilla, android, opera, edge (development), safari (development)
main.js
const registration = await navigator.serviceWorker.ready
const push = await registration.pushManager.subscribe()
sendToBackend(push.endpoint)
manifest.json
(chrome only)
{ ..., "gcm_sender_id": "<Your Sender ID Here>" }
--
- Background sync wait for stable network connection (backoff)
- Periodic background sync scheduled synch (in design)
- Background fetch HTTP request outlives browser close
πΆ Background-fetching proposal
--
- Written in Markdown with Cleaver
- Graphs with Mermaid, code with Highlights
- Theme Select, icons UTF8 with Symbola font
- Jake Archibald, author/advocate of specs
- Google developers website & MDN
- w3c & whatwg
--
--
sequenceDiagram
participant B as π Browser
participant W as π Service Worker
participant S as β‘ Push Server
B->>W: register
activate B
activate W
W--xB: message
deactivate W
B--xW: fetch
activate W
deactivate W
deactivate B
S--xW: notification
activate W
W--xB:
deactivate W