Skip to content

Instantly share code, notes, and snippets.

@sarangkartikey50
Created May 3, 2020 15:36
Show Gist options
  • Save sarangkartikey50/a9c7c714b920a4470b402121457aeb76 to your computer and use it in GitHub Desktop.
Save sarangkartikey50/a9c7c714b920a4470b402121457aeb76 to your computer and use it in GitHub Desktop.

1. Register

- parse and register /sw.js
    a. Install
        - install the service worker.
    b. Installed
        - ther service worker was installed successfully and waiting for activation (some other instance service worker is installed)

2. Activated.

- Service worker is activated and is in use.
1. Intercept `fetch` event.
2. access request object from `e.request`.
3. send intercepted response with `e.respondWith`.

Scope

Service worker's scope can be restricted using the second argument of register().

navigator.serviceWorker.register('./sw.js', '/posts')

In above registration, the service worker scope is restricted to /posts and the service worker can't handle request outside that scope.
Registration object can be used to access the lifecycle of the service worker. We can listen to `onupdatefound` event to check for updates. `onstatechange` for accessing state change. For eg., installed -> activating -> activated.
Service workers use HTML 5 messaging api to communicate across service workers or between service worker and clients. For eg.,
  1. When we want to prompt user about an update available.
//main.js
restration.onupdatefound = () => {
	const newSw = registration.installing;
	if (confirm('App update available. Press ok to install!'))
		newSw.postMessage('SKIP_WAITING');
};

//sw.js
self.addEventListener('message', (e) => {
	if (e.data === 'SKIP_WAITING') self.skipWating();
	//This will activate the service worker immediately on user propmp.
});
  1. When we want to communicate from service worker to client.
//main.js
navigator.serviceWorker.addEventListener('message', (e) => {
	console.log(e.data);
});

//sw.js
self.addEventListener('activate', (e) => {
	self.clients.then((clients) => {
		clients.forEach((client) => {
			// sends message to all service worker
			client.postMessage('Message from service worker');

			//sends message to current client
			if (e.source.id === client.id)
				client.postMessage('Private Message from service worker');
		});
	});
});
Notifications api is used to show push notifications when the user not active on our app.
A user can grant three permissions -
1. granted
2. denied
3. default (Ask)

The timing for asking for permission is very important. We should never ask the permissions directly. Instead show custom dialog for permissions. If user is interested, then only ask for permissions. If user block or denies for notifications, then you'll never be able to send push notifications.
Eg.,
//main.js
if (window.Notification) {
	if (Notification.permission === 'granted') {
		// showNotification();
	} else {
		Notification.requestPermission().then(() => {
			if (Notification.permission === 'granted') {
				// showNotification();
			}
		});
	}
}

//sw.js
self.addEventListener('push', (e) => {
	const options = {
		body: 'body',
		icon: './logo.png',
	};
	e.waitUntil(
		self.registration.showNotification('title', options);
	);
});
Number of storage options are available for storing files.
Cache storage is the most suitable w.r.t PWAs, because it use request - response as key - value.
it has following methods:
	//opening a cache for reading, writing.
	caches.open(cacheName);

	//adding files to cache
	caches.open(cacheName)
		.then(cache => cache.add('/'));

	//deleting a cache
	caches.delete(cacheName);

	//updating a cache
	caches.open(cacheName)
		.then(cache => cache.put('/', Response));

	//getting all cache names (keys)
	caches.keys().then(keys => console.log);

	//matching files or request inside a cache
	caches.open(cacheName)
		.then(cache = {
			cache.match(e.request);
		});

Cache with Service Worker

With service worker, cache can be used to intercept network request and serve data.
Caches can be created during the `install` event.
Caches can be garbage collected in the `activate` event.
Eg.,
const cacheName = 'v1';
const staticCache = ['/', '/index.html', '/index.js', '/logo.png'];

self.addEventListner('install', (e) => {
	e.waitUntil(
		caches.open(cacheName).then((cache) => cache.addAll(staticCache))
	);
});

self.addEventListener('activate', e => {
	e.waitUntil(
		caches.keys().then(keys => {
			keys.forEach(key => {
				if(key !== cacheName)
					return caches.delete(key);
			});
		})
	);
});

self.addEventListener('fetch', e => {
	e.waitUntil(
		caches.open(cacheName)
			.then(cache => {
				cache.match(e.request)
					.then(res => {
						if(res) return res;
						return fetch(e.request)
							.then(fetchRes => {
								cache.put(e.request, fetchRes.clone());
								return fetchRes;
							});
					});
			});
	);
});

Cache Strategies

1. Cache only
	- serve data from cache only.

2. Cache with network fallback.
	- serve data from cache, if not availabe make network call.
	- In this strategy data will never be updated if it's in the cache.

3. Network with cache fallback.
	- serve from network. If request fails, serve from cache.
	- This will result in very poor UX. Because network requests might take time.

4. Cache with Network updates.
	- serve from cache. Make parallel network calls and update the cache.
	- In this way user will either get one request behind content.

5. Cache and Network race
	- Make promises of both cache and network request simultaneously. Which even resolves first, serve.
	- This is the best strategy. 
	- Also adding fallback static data will be an advantage.

Make Installable

A Pwa is installable on when it meets following criteria:

1. It should have a valid service worker.
2. It should serve over https.
3. It should have a manifest.json file with atleast 
	name, short_name, start_url, icons 144x144.

For safari:
  1. startup image link
	<link rel="apple-touch-startup-image" href="/splash.png" />
  1. meta tag for the title of app
	<meta name="apple-mobile-web-app-title" content="Pwa" />
  1. meta tag for launching app standalone
	<meta name="apple-mobile-web-app-capable" content="yes" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment