15 Oct 2019

Working with Viewer data "Serverlessly" - thumbnails, screesnshots and serverless upload

Traditionally, it might sound tricky to implement workflows like displaying thumbnails, snapshots, uploaded derivatives and performing model data queries that appear to be light weight without involving the backend, consuming extra time and cost. However in truth if we make good use of a few well defined APIs (namely File, Blob, Streams and Uint8Array etc) that come with modern browsers to deal with binary data, these tasks can actually be implemented mostly in client side with minimal reliance on server side backends - let's see how we can make these workflows "serverless" (aka. w/o backend and not the ever so hot FaaS) today.

Display Extracted Thumbnails  "Serverlessly"

Usually to display the thumbnail extracted for our models we'd resort to the backend to fetch the image with a read scoped token, download or relay the image for our web apps since it’s difficult to take the backend out of the equation due to the fact HTML’s native image element does not support header authentication as is required by OAuth. However actually we can overcome this predicament by fetching the thumbnail with AJAX and pass the response payload to an image element as data URL. Here’s how it works:

xmlHTTP.responseType = 'arraybuffer';
xmlhttp.setRequestHeader("Authorization", "Bear <token goes here>");

xmlHTTP.onload = function(e)

        const arr = new Uint8Array(this.response);

        const raw = String.fromCharCode.apply(null,arr);

        const dataURL="data:image/jpeg;base64,"+ btoa(raw);
        document.getElementById("image").src = dataURL;

And our AJAX handler can be significantly simplified if we throw Axios into the mix: 

axios.get(url, {
 headers:{Authorization: “Bearer <token goes here>”},
      responseType: 'arraybuffer'
    .then(response => new Buffer(response.data, 'binary').toString('base64'))


Display Viewer's Screenshots "Serverlessly"

Similarly we can display a screenshot of Viewer's canvas on the fly - simply take a snapshot with Viewer after it's taken and create an image element to load the Blob URL:

NOP_VIEWER.getScreenShot(width, height, blobURL=>{

  const image = new Image();
  image.src = blobURL;

And as most of you must know we can load the Blob URL given by Viewer as any other URL and read its contents from browser memory and store to our own sources:

xhr.open('GET', 'blob:url', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
  if (this.status == 200) {
    //this.response; blob data in memory


Upload and Visualize Derivatives "Serverlessly"

Now let's crack things up a notch - wouldn’t it be fantastic if we could upload a PDF, F2/3D or even SVF archive and load it with Viewer without bothering with Forge or even a single handler of your backend? With the File and Blob APIs we can certainly take care of the uploads but we still need to feed the binary to Viewer - for this we employ our good old pal Service Worker to intercept Viewer’s requests – same trick you must have grown quite accustomed if you’ve read Disconnected Workflow and Implement retry, custom query string and more for Viewer:

\\Your Service Worker
const binaryStore = {}

self.onmessage = function (e) {
  binaryStore[e.data.path] = e.data.payload

self.addEventListener('fetch', event => {
    async function() {
	const matches = /http\:\/\/(\w|\:|\.)+\/models\/(.+)/.exec(event.request.url)
		const path = matches.pop();
        return new Response(binaryStore[path])
      return fetch(event.request)

And convert the file input to binary array to post to Service Worker with:

const file = document.querySelector('input').files[0];
const reader = new FileReader();
const fileByteArray = [];

reader.onloadend = function (evt) {
    if (evt.target.readyState == FileReader.DONE) {
           const array = new Uint8Array(evt.target.result);
       for (var i = 0; i < array.length; i++) {

navigator.serviceWorker.controller.postMessage({payload: fileByteArray, path: file.name})

Also we can even use jszip (https://github.com/Stuk/jszip) (which works with all major browsers) to read contents from the archive and feed them to Viewer as requested, this can be used for SVF archives extracted using your own code or our extract.io:

const new_zip = new JSZip();
new_zip.loadAsync(binaryArray).then(function(zip) {
    zip.forEach(function (relativePath, file){
       //post contents to service worker


Query Property Data Serverlessly

Finally, how about our perpetual need to converse extensively with model data so it is possible to query that entirely on the client side? Well we have the SQLite version of the model database and the mighty WebAssembly (that we discussed in details a little while ago here) to thank for making this possible.

As many of you may know - You can download flat SQLite and find the meta data there - download it via urn-manifest-derivativeurn-GET and can find its URN via the manifest endpoint and it should look like:

                    "guid": "...",
                    "type": "resource",
                    "urn": "urn:adsk.viewing:fs.file:<urn>/output/properties.db",
                    "role": "Autodesk.CloudPlatform.PropertyDatabase",
                    "mime": "application/autodesk-db",
                    "status": "success"

As a point worth of note, along side our usual model data in this version of the property DB there’s also valuable meta data including display unit, , and other useful info.

Then we can load the database by converting its binary like we mentioned before and query this SQLite property DB in the browser using sql.js and here’s how our code looks:

      const db = new SQL.Database(PDBBinaryArray);
      db.run("Your query goes here");



Honky tonky - that's all we have time for today and next time we will revisit our real, charming Serverless (as in FaaS this time) later to explore hosting Viewer apps with AWS & Azure's Faas offerings. Thanks and until next time!







Related Article