Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I install both a service worker and a manifest with a single Javascript tag?

I've built a service for websites to easily integrate push notifications into their site but the current Chrome implementation also requires they set up a manifest and service worker.

Is there any way to set up the service worker and manifest with only a single line of Javascript?

like image 325
owencm Avatar asked Mar 16 '23 11:03

owencm


1 Answers

Yes you can reduce the steps to getting them to host a service worker file on their origin and include a single line of Javascript in their page.

You can do it by using the Javascript they include to:

  1. Register a service worker
  2. Inject a link to a non-existent manifest into the head
  3. Intercept the request to that manifest with your service worker and respond with your custom manifest

Your Javascript should look something like this:

navigator.serviceWorker.register('sw.js').then(function(registration) {
    console.log('ServiceWorker registration successful with scope: ', registration.scope);
    var head = document.head;
    var noManifest = true;
    // Walk through the head to check if a manifest already exists
    for (var i = 0; i < head.childNodes.length; i++) {
        if (head.childNodes[i].rel === 'manifest') {
            noManifest = false;
            break;
        }
    }
    // If there is no manifest already, add one.
    if (noManifest) {
        var manifest = document.createElement('link');
        manifest.rel = 'manifest';
        manifest.href = 'manifest.json';
        document.head.appendChild(manifest);
    }
});

Note how I've avoided adding a manifest tag if one already exists.

Your service worker should look something like:

self.addEventListener('fetch', function(e) {
    if (e.request.context === 'manifest') {
        e.respondWith(new Promise(function(resolve, reject) {
            fetch(e.request).then(function(response) {
                if (response.ok) {
                    // We found a real manifest, so we should just add our custom field
                    response.json().then(function(json) {
                        json.custom_field = 'Hello world';
                        var blob = new Blob([JSON.stringify(json)], { type: 'application/json' });
                        console.log('Appended a custom field to the pre-existing manifest');
                        resolve(new Response(blob));
                    });
                } else {
                    // There was no manifest so return ours
                    console.log('Injected a custom manifest');
                    resolve(new Response('{ "custom_field": "Hello world" }'));
                }
            });
        }));
    }
});

// These pieces cause the service worker to claim the client immediately when it is registered instead of waiting until the next load. This means this approach can work immediately when the user lands on your site.
if (typeof self.skipWaiting === 'function') {
    console.log('self.skipWaiting() is supported.');
    self.addEventListener('install', function(e) {
        // See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-skipwaiting
        e.waitUntil(self.skipWaiting());
    });
} else {
    console.log('self.skipWaiting() is not supported.');
}

if (self.clients && (typeof self.clients.claim === 'function')) {
    console.log('self.clients.claim() is supported.');
    self.addEventListener('activate', function(e) {
        // See https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#clients-claim-method
        e.waitUntil(self.clients.claim());
    });
} else {
    console.log('self.clients.claim() is not supported.');
}

Note how it only intercepts requests for manifests, and checks if one actually exists. If it does, the service worker simply appends our custom fields to it. If the manifest doesn't already exist we return our custom field as the whole manifest.

Update (August 25th 2015): I just read that Request.context has been deprecated so unfortunately you will need to find a new way of discovering whether a request is for a manifest or something else in place of the line if (e.request.context === 'manifest').

like image 129
owencm Avatar answered Mar 18 '23 01:03

owencm