Append string to user agent within Service Worker

Viewed 2847

I've been stuck on this problem for a while now. I have researched in depth and have spent a lot of time looking at similar questions on SO - but unsuccessful.

A bit of background. I have a website and an android app which effectively renders the website in a different form with different displays etc... The app knows to do this from incoming requests - as users have a particular string appended to the user agent (let's just say that string is 'MobileAppRequest'). Upon detecting this string in the user agent, the server knows to return a different html file. This allows the user to still go to the website on their browser and use the web version as well as have the app experience from their android app.

Now, upon using a service worker, it defaults to the user's standard user agent without including the appended string from the app. This then returns the web version within the app which messes it all up. I need to know how to set custom headers, or indeed, append a string to the user agent within the service worker. It says the headers are immutable when i try to directly change it but i know a way around this is to make a new request as a response.

Here is my SW.js

var CACHE_STATIC_NAME = 'static-v10';
var CACHE_DYNAMIC_NAME = 'dynamic-v2';

self.addEventListener('install', function (event) {
  console.log('[Service Worker] Installing Service Worker ...', event);
  event.waitUntil(
    caches.open(CACHE_STATIC_NAME)
      .then(function (cache) {
        console.log('[Service Worker] Precaching App Shell');
        cache.addAll([
          '/',
          '/static/media/next.png',
          '/static/media/previous.png'
        ]);
      })
  )
});

self.addEventListener('activate', function (event) {
  console.log('[Service Worker] Activating Service Worker ....', event);
  event.waitUntil(
    caches.keys()
      .then(function (keyList) {
        return Promise.all(keyList.map(function (key) {
          if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
            console.log('[Service Worker] Removing old cache.', key);
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request)
      .then(function(res) {
        return caches.open(CACHE_DYNAMIC_NAME)
                .then(function(cache) {
                  cache.put(event.request.url, res.clone());
                  return res;
                })
      })
      .catch(function(err) {
        return caches.match(event.request);
      })
  );
});

I would appreciate the answers contain either the full sw.js or the whole function you're editing just so i don't make any mistakes integrating back into the code.

2 Answers

As I mentioned in the comments, you can pass the whatever data you want to the Service Worker if you use postMessage().

Below is an example using your script.

webpage.html

<script>
  // creates ServiceWorker
  var worker = new Worker('SW.js');
  // sends the browser's userAgent to the ServiceWorker
  worker.postMessage(window.navigator.userAgent);
</script>

SW.js

var CACHE_STATIC_NAME = 'static-v10';
var CACHE_DYNAMIC_NAME = 'dynamic-v2';
// defines a global variable that will hold the UserAgent
var UA = '';

// listens to incoming postMessage()
self.addEventListener("message", function(event) {
  // update the UA variable with the event.data, which is the browser's UserAgent
  UA = event.data;
  console.log('ServiceWorker now has UA variable defined as ', UA);
}, false);

self.addEventListener('install', function (event) {
  console.log('[Service Worker] Installing Service Worker ...', event);
  event.waitUntil(
    caches.open(CACHE_STATIC_NAME)
      .then(function (cache) {
        console.log('[Service Worker] Precaching App Shell');
        cache.addAll([
          '/',
          '/static/media/next.png',
          '/static/media/previous.png'
        ]);
      })
  )
});

self.addEventListener('activate', function (event) {
  console.log('[Service Worker] Activating Service Worker ....', event);
  event.waitUntil(
    caches.keys()
      .then(function (keyList) {
        return Promise.all(keyList.map(function (key) {
          if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
            console.log('[Service Worker] Removing old cache.', key);
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(event) {
  // var myHeader = new Headers(event.request.headers);
  // myHeader.append('CUSTOMHEADER', 'CUSTOMVALUE');
  // you can now make a request using the passed UA
  event.respondWith(
    fetch(event.request, {credentials: 'same-origin', mode: 'cors', redirect: 'follow', userAgent: UA}).then(function(res) {
        return caches.open(CACHE_DYNAMIC_NAME)
                .then(function(cache) {
                  cache.put(event.request.url, res.clone());
                  return res;
                })
      })
      .catch(function(err) {
        return caches.match(event.request);
      })
  );
});

While browsing SO is great for examples, sometimes it's better to reference the documentation when you have limited options.

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

I've edited your code to add custom header in fetch request. You should try with it, It'll work.

For your use case, you want to add custom header for your domain request only, so I've added a regex check which will check and add custom header to fetch requests pertaining to your domain only. (added localhost for local testing). For fetch requests pertaining to other domain, it'll send standard headers.

You need to pass some additional parameters to fetch call, which I've added. In you application, edit 'CUSTOMHEADER', 'CUSTOMVALUE' and YOURDOMAIN.com as you want.

Read more about Header() at https://developer.mozilla.org/en-US/docs/Web/API/Headers. In case you want to modify useragent, add following lines in fetch event in the answer,

  var myHeader = new Headers(event.request.headers);
  myHeader.delete('User-Agent');
  myHeader.set('User-Agent', 'CUSTOM_USERAGENTVALUE');

Please Note: Altering "User-Agent" is not recommended as of now, as recently it was removed from list of prohibited header. So browser support is limited.

var CACHE_STATIC_NAME = 'static-v10';
var CACHE_DYNAMIC_NAME = 'dynamic-v2';

self.addEventListener('install', function (event) {
  console.log('[Service Worker] Installing Service Worker ...', event);
  event.waitUntil(
    caches.open(CACHE_STATIC_NAME)
      .then(function (cache) {
        console.log('[Service Worker] Precaching App Shell');
        cache.addAll([
          '/',
          '/static/media/next.png',
          '/static/media/previous.png'
        ]);
      })
  )
});

self.addEventListener('activate', function (event) {
  console.log('[Service Worker] Activating Service Worker ....', event);
  event.waitUntil(
    caches.keys()
      .then(function (keyList) {
        return Promise.all(keyList.map(function (key) {
          if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
            console.log('[Service Worker] Removing old cache.', key);
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

self.addEventListener('fetch', function(event) {
    if(/^.*\:\/\/(?:localhost|YOURDOMAIN\.com)\/.*/.test(event.request.url))
    { var myHeader = new Headers(event.request.headers);
      myHeader.append('CUSTOMHEADER', 'CUSTOMVALUE');
      console.log('Custom Fetch Call:'+event.request.url);
      event.respondWith(
        fetch(event.request.url, {credentials: 'same-origin', redirect: 'follow', headers: myHeader}).then(function(res) {
            return caches.open(CACHE_DYNAMIC_NAME)
                    .then(function(cache) {
                      cache.put(event.request.url, res.clone());
                      return res;
                    })
          })
          .catch(function(err) {
            return caches.match(event.request);
          })
      );
      return
    }
    event.respondWith(
      fetch(event.request).then(function(res) {
        return caches.open(CACHE_DYNAMIC_NAME)
                .then(function(cache) {
                  cache.put(event.request.url, res.clone());
                  return res;
                })
      })
      .catch(function(err) {
        return caches.match(event.request);
      })
    );  


});

You can check it on service echo $_SERVER['HTTP_CUSTOMHEADER']; , it will echo CUSTOMVALUE. I've cross verified its working through server side and on chrome network panel.

In order to serve custom app page to your android user you should use (IN PHP):

$INSIDEAPP = (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'],'CUSTOMUSERAGENT') !== false))? true:false;
$INSIDE_SW = (isset($_SERVER['HTTP_CUSTOMHEADER']))?true:false;

$SERVE_APP_PAGE = $INSIDEAPP || $INSIDE_SW;

if($SERVE_APP_PAGE)
{
//serve request of app page.
}
else
{
// serve normal request through browser
}
Related