We all enjoy using our favorite web applications to work, communicate, relax, and more. But what happens when an Internet connection is not available? Simply put, if we can’t access the web, we can’t access our web applications, as they heavily depend on data and services running on the cloud.
Several attempts have been made to address this issue, with the ultimate goal to allow web applications to perform some work while offline (does the name AppCache ring any bells?).
ServiceWorker technology was conceived with the same goal, allowing the developer to write an intermediate layer between the application running on the client and the data stored elsewhere.
Designed as a data proxy, a ServiceWorker is used to cache data locally, with application dependent logic.
But ServiceWorkers are much more than that. They are, in essence, a tool to execute client side code beyond the scope of a single web page.
Trent Willis, senior engineer at Netflix, showcased some interesting scenarios at Codemotion Milan 2018.
// Fullfills a request for an encrypted image with a normal PNG response.
// Does so by loading the encrypted image data, decrypting it, and then putting it into
// a PNG blog which the browser knows how to render.
const pngFromEncryptedImageRequest = async (request) => {
const response = await fetch(request);
const encryptedData = await response.arrayBuffer();
const decryptedData = await decryptData(encryptedData);
const pngBlob = new Blob([decryptedData], { type: ‘image/png’ });
return new Response(pngBlob);
};
Snippet 1: custom file decryption in a ServiceWorker
Since a ServiceWorker sits between the application and the server, it represents the perfect place to implement client side data processing, while keeping the page responsive.
As an example, a ServiceWorker could be used to decode an image format that is not natively supported by the browser. With a few lines of code, we can implement a polyfill for that format, translating the data received from the server into something that the browser can interpret.
To avoid extra computational overhead, the decoded compatible image can be then cached using traditional caching techniques typical of ServiceWorkers applications.
Another interesting use case is encryption. ServiceWorkers allow implementing E2EE (End To End Encryption) in a very flexible and efficient way, without depending on the facilities offered by the browser.
ServiceWorker technology is therefore a great tool to develop fewer browser-dependent applications. They provide developers all the flexibility they need without compromises on security.
Compared to traditional approaches designed to overcome browsers limitations (applets, plugins, extensions, etc), they are application-wide rather than browser-wide and therefore easier to maintain and deploy.
At this point, the reader may wonder how all this client side processing can be implemented efficiently, considering the limitations imposed by the browser.
The most common technique is to pipeline data processing as much as possible. A naive processing implementation for a ServiceWorker would simply fetch an entire response from the server, process its body and then generate a reply for the browser. This approach leads to twice the memory usage, poor single-threaded performance and, in some cases, can cause the browser page to freeze for some time.
The streaming API comes to the rescue, as a very powerful tool to implement efficient pipelined (stream oriented) processing. Basically, data is processed in small chunks; while processing one chunk, the next one can be fetched. This reduces drastically the memory consumption and decreases the overall execution time.
Moreover, developers can leverage multithreading, at the least to some extent, using WebWorkers. WebWorkers allow our application to run tasks in the background. Under the hood, the browser spawns another thread and runs the code worker under it.
Communication between WebWorkers and ServiceWorkers can be implemented using the messaging API, as objects shared via postMessage() calls can be transferred from one context to another safely.
All the tools described above allow front-end developers to write efficient, streamlined applications without compromising portability and browsers compatibility.