26 Sep 2019

Implement retry, custom query string and more for Viewer with Service Worker

In our previous articles over the capabilities of modern APIs for building apps with Forge and Viewer, we have explored the possibilities of leveraging Service Worker to enable offline workflows and even naturalize Viewer web apps for cross platform devices. But if you ever wondered whether it is possible to go further ahead to implement retry pattern, inject query strings and even en/decryption with Viewer then that’s exactly (well maybe not exactly the whole package for now) our quest today.

Automatic Retry for Loading Models

Since Service Worker opens the door for AOP (Aspect Oriented Programming) with its event handlers to intercept browser requests, we can easily put in our own control flow to, say attempt retries in the event of a failed request to load model files on Forge or your own storage. Here’s the code I whipped up:

self.addEventListener('fetch', event => {
  event.respondWith(
    async function() {

      if(/http\:\/\/(\w|\:)+\/models/.test(event.request.url))   // match all path under '/models'
      {

        let response = await fetch(event.request);
        let retrys = 3; // try 3 times
        while(response.status > 399&&retrys-->0)  // check if failed
        {
          console.log(retrys, response);
          response = await fetch(event.request)
        }
        return response
      }
      else
      return fetch(event.request)
}())
});

And hooray - it works:

sb233

Along this line we can come up with tailored mechanisms to accommodate for challenging networking environments and achieve circuit breakers and client-side route switching for load balancing and redundancy purposes.

Custom Query Strings and Headers

When loading model files from our own servers, it’s only part of human nature to inject custom query parameters and headers for authentication and purposes isn’t it? Sadly Viewer does not exactly offer (explicitly) an option to achieve this, but with Service Worker working around this becomes a matter of simple code like below:

self.addEventListener('fetch', event => {
  event.respondWith(
    async function() {

      if(/http\:\/\/(\w|\:)+\/models/.test(event.request.url))
      {

        const request = event.request;
        const headers = request.headers
        const url = new URL(request.url);

        url.searchParams.append('test',233);  // assemble the query string

        const newReq = new Request(url.toString());  //the original request is immutable so be sure to start anew
        headers.keys(k=>newReq.headers.append(k, headers.get(k))); // copy the original headers over
        newReq.headers.append('test',233);   // add custom headers
        return newReq
      }
      else
      return fetch(event.request)

}())
});

And so it works:

sb233

But in all fairness Viewer does allow us to set custom headers with the below option that’s a bit off the beaten track: 

Autodesk.Viewing.Initializer(options, function(){
          Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS = {'X-My-Custom-Header':'233', ...}
...

So suit your fancy with either approaches…

En/decryption

Finally did you that it’s even possible to modify the response stream with Service Worker?! With this we’d be able to have our model files encrypted at all times during persistence and transmission, and decrypt them on the fly for consumption by Viewer as they reach the client:

const response = await fetch(event.request);
const reader = response.body.getReader();
  const stream = new ReadableStream({
    start(controller) {
      // The following function handles each data chunk
      function push() {
        // "done" is a Boolean and value a "Uint8Array"
        reader.read().then(({ done, value }) => {
          // Is there no more data to read?
          if (done) {
            // Tell the browser that we have finished sending data
            controller.close();
            return;
          }

          // Get the data and send it to the browser via the controller
          controller.enqueue(value);
          push();
        });
      };
      
      push();
    }
  });

  return new Response(stream, { headers: { "Content-Type": "text/html" } });

But you might wonder if that’d be worth our while with the script code itself in plain text the en/decryption logic would be transparent to the users as well and hence defeating our very purpose - well let’s save this topic for a little later when we discuss our WebAssembly venture with Viewer in our upcoming articles so stay tuned.

That’s all we have time for today! Should ideas of any ingenuity level to empower Viewer with Service Worker pop into that brilliant mind of yours please do not hesitate to drop us a line at forge.help@autodesk.com - we are all ears. Thanks and until next time!
 

Related Article