Sep 03, 2019

Generating Previews of User Submitted Markup - GCP, Firebase and Pupeteer, Oh my!

Headless chrome has been in the toolbox of every great web engineer for some time now, and the introduction of Puppeteer has allowed deep, meaningful integrations into build and test pipelines. What I see far more rarely, however, is headless chrome shining bright outside of these use-cases - luckily the project I am working on has a dark underbelly to illuminate: user generated content.

Imagine you have a situation in which your end-users can produce HTML - maybe not directly, maybe the HTML is generated from Markdown or some other source - but at the end of the workflow, you have markup. Also imagine users will have their own libraries of markup and corresponding data and you will need to allow them to easily preview and browse their collection (The floor is made of NDA lava) and you can start to see the use-case for static, pre-rendered previews.

Fortunately, Google Cloud will make all of this very easy for us: serverless functions that support Puppeteer out-of-the-box and Firebase which will trigger said serverless functions effortlessly. Step 1, head over to https://console.firebase.google.com/ and create a new project. Fire up VSCode or your preferred IDE of choice and get into a new workspace.

npm i -g firebase-tools
firebase init

Follow the prompts and be sure to initialize ‘hosting’, ‘firestore’, ‘functions’ and ‘storage.’ Google Cloud’s serverless functions require you to install dependencies and compile locally before deploying. From the command line, step into your ‘functions’ directory that the CLI created for you. We’ll need to pull down three libraries, the first is Puppeteer - the second is ‘child-process-promise’ which does exactly as it advertises, allowing us to spawn a process and receive it’s output as a promise. GCP installs the ImageMagik CLI by default and we’ll want to capitalize on that by compressing our generated screenshots. Lastly, we’ll install ‘firebase-admin’ which handles a lot of the necessary setup automagically.

cd functions
npm i --save puppeteer
npm i --save child-process-promise
npm i --save firebase-admin

Awesome! Now that those copy and paste muscles are all fired up, let’s open up ‘index.js’ under the functions directory - we’ll want to import our dependencies, initialize our app and create a shell of a function:

    const os = require('os');
    const fs = require('fs');
    const admin = require('firebase-admin');
    const functions = require('firebase-functions');
    const puppeteer = require('puppeteer');
    const spawn = require('child-process-promise').spawn;

    admin.initializeApp();

    exports.screenshotter = functions.firestore
    .document('users/{user}')
    .onUpdate(async (change, context) => {

    });

So in ~10 lines of code we have our dependencies required, our app acquiring IAM automagically, and a function setup to trigger when any document underneath the ‘users’ collection gets modified. Easy peasy, lemon squeezy - “but wait!” the astute reader among you is thinking, “what ‘users’ collection?” Good catch! We’ll need to log into the firebase console (or switch open to the tab from earlier, https://console.firebase.google.com/) and head over to the “Database” section under “Develop,” right at the top. Create a new database and add a collection to it named “users.”

Now everything is setup and we can make Puppeteer work for us! POWER

Let’s start by grabbing the data we need, both pre-change and post-change - and critically - let’s check if we can fail fast, fail early:

    exports.screenshotter = functions.firestore
    .document('users/{user}')
    .onUpdate(async (change, context) => {
        
        const oldDoc = change.before.data();
        const newDoc = change.after.data();

        // If the field we are actioning on has not changed, bail
        if (oldDoc.markup === newDoc.markup) return;

        // Always handle exceptions gracefully! For illustration only, do better! Clean up / release resources!
        const simpleExceptionHandler = err => {
            console.error(err);
            throw err;
        };

        // Initialize Puppeteer
        const browser = await puppeteer.launch({
            args: [
              '--no-sandbox',
              '--headless',
              '--hide-scrollbars',
              '--mute-audio',
              '--disable-gpu'
            ]
        }).catch(simpleExceptionHandler);

        const page = await browser.newPage().catch(simpleExceptionHandler);
    });

Once we determine we need to continue, we launch Puppeteer and wait for a new page. Puppeteer has a myriad of options and command line arguments - if you’d like to check them out and tailor to your environment, I encourage you to RTFM @ https://github.com/puppeteer/puppeteer - but for now just roll with these sane defaults.

After waiting for headless chrome to ‘boot up’ we can now wait for viewport readiness and fill it with content. In the example below I am pulling the size from user settings, but you can of course pull from anywhere you’d like. You’ll also notice I am feeding html directly from our firestore document into our page, whereas in production you’d likely not be doing this (illustration purposes only, wit.soul === brevity):

    
    const page = await browser.newPage().catch(simpleExceptionHandler);

    await page.setViewport({
        width: newDoc.settings.screenshot.width || 1200,
        height: newDoc.settings.screenshot.height || 900,
        deviceScaleFactor: 1,
    }).catch(simpleExceptionHandler);

    await page.setContent(newDoc.markup, {
        waitUntil: "networkidle0" // Wait until all requests are quiet for 500ms, i.e. all page assets fetched
    }).catch(simpleExceptionHandler);
    

Awesome! At this point, somewhere on a virtualized piece of memory, deep, deeeeep within a Google Data Center, our headless chrome instance has our page fully rendered. Now we’ll need to grab a scratch location to put our images, await Puppeteer following our screenshot command, then use GCP’s pre-installed ImageMagik to create a thumbnail:

    await page.setContent(newDoc.markup, {
        waitUntil: "networkidle0"
    }).catch(simpleExceptionHandler);

    // Ensure we can write bytes to this location
    const originalScreenshot = path.join(os.tmpdir(), 'original.png');
    const modifiedScreenshot = path.join(os.tmpdir(), 'modified.jpg');

    await page.screenshot({ path: originalScreenshot }).catch(simpleExceptionHandler);

    // Thumbnail ImageMagik
    await spawn('convert', [
        originalScreenshot,
        '-resize',
        `${newDoc.settings.thumbnail.width || 400}x${newDoc.settings.thumbnail.height || 300}`,
        '-quality',
        '92',
        modifiedScreenshot
    ], { capture: ['stdout', 'stderr'] }).catch(simpleExceptionHandler);
    

So now we have two beautiful images sitting in a temp directory on an ephemeral linux vm. Let’s show off our work! We’ll need to send these bad boys to Google Cloud Storage so they can be accessed by the public, writ large:

    // Thumbnail ImageMagik
    await spawn('convert', [
        originalScreenshot,
        '-resize',
        `${newDoc.settings.thumbnail.width || 400}x${newDoc.settings.thumbnail.height || 300}`,
        '-quality',
        '92',
        modifiedScreenshot
    ], { capture: ['stdout', 'stderr'] }).catch(simpleExceptionHandler);

    // Get a reference to our default bucket
    const cloudBucket = admin.storage().bucket();
    
    // Wait for thumbnail upload
    await cloudBucket.upload(modifiedScreenshot, { 
        destination: `thumbnails/${change.after.id}.jpg` 
    }).catch(simpleExceptionHandler);
    
    // Wait for screenshot upload
    await cloudBucket.upload(originalScreenshot, { 
        destination: `screenshots/${change.after.id}.jpg` 
    }).catch(simpleExceptionHandler);

    // Be kind, rewind! (Clean up scratch on VM)
    fs.unlinkSync(originalScreenshot);
    fs.unlinkSync(modifiedScreenshot);

    // Release the browser!
    await browser.close().catch(simpleExceptionHandler);

    // End func
    return true;
    

Once we have waited for Google’s storage library to do the heavy lifting for us, we simply clean up after ourselves and power down. Keep in mind you would also need to make your cloud bucket publicly accessible by giving ‘allUsers’ read access in it’s AIM policy. Additionally, you would need some business logic to get that url into your application, perhaps updating the users firestore document? That goes beyond the scope of this post; The choice is yours.



Feb 21, 2016

Introducing: Assembled Realms, Build and Play Open-Source MMOs - All From A Browser

When I was eleven years old I had an experience that would eventually set the course for my entire professional life. It was my first day of class in the 6th grade. The middle school was experimenting with a new curriculum that would expose gifted students to a computer in every classroom, the lion-share of which being the venerable Macintosh Classic II.

I immediately began tinkering. Lessons were completed as quickly as possible to afford me more time on the machines. I stayed after school learning AppleScript. I’d create simple slide-based adventure games for my friends to play and solve (think Myst written by a little kid with no graphics at all). I wanted to create more advanced games so I checked out a book from the library that would teach me how to write BASIC. The book had full source code examples that I hungrily reverse engineered and re-imagined.

This early passion to figure out how things work and to make things my friends could enjoy ignited a life long career in software development. Assembled Realms hopes to offer that same sense of wonder and opportunity with a significantly lower barrier to entry. The idea is to provide structured game “engines”, essentially mini working games, to users that serve as the foundation for experimentation, that can be picked apart or completely demolished and rebuilt.

GAME

All client and server code is written in Javascript and can be created/edited directly from a browser. No software to download, no platform restrictions. Realm builders can launch their games with a click of a button, receiving a public URL to give to friends. Bugs and improvements can be suggested by the user base, pushing the realm builder to research, explore and learn. Users are introduced to concepts essential to the software development lifecycle - committing, publishing, debugging, etc - in a fun and effective way.

IDE

I’m excited to see what the community can produce and am making myself available to help in any way I can.

Assembled Realms

@chasebgale



Oct 21, 2011

Introducing: Rocketcharts, Open-source HTML5 Financial/Statistical Charts

Utilizing HTML5′s canvas, I present to you my newest open-source project: rocketcharts. I am following the “release early, release often” mantra for this project, so I’d keep it out of production code for the moment. Speaking of code, how did I generate the above interactive chart?

<script type="text/javascript" src="rocketcharts.js"></script>
<script type="text/javascript" src="jquery-1.5.1.min.js"></script>
<script type="text/javascript">
   $(document).ready(function () {    
 
      var style = new Object();
      style.UpColor = {r: 0, g: 255, b: 0};
      style.DownColor = {r: 255, g: 0, b: 0};
 
      var settings = new Object();
      settings.customUI = true;
      
      var rocketcharts = new Rocketchart();
 
      rocketcharts.init(document.getElementById("rocketchart"), settings); 
 
      var googData = [{date: "11/9/2010", open: 17.22, high: 17.6, low: 16.86, close: 16.97, volume: 56218900},
                      {date: "11/10/2010", open: 17, high: 17.01, low: 16.75, close: 16.94, volume: 17012600},
                      ...
                      {date: "9/29/2011", open: 14.34, high: 14.39, low: 13.15, close: 13.42, volume: 45776600},
                      {date: "9/30/2011", open: 13.21, high: 13.44, low: 13.11, close: 13.17, volume: 30232800}];
 
      // rocketcharts.addSeries(title, data, type, style, panel);
      rocketcharts.addSeries("GOOG", googData, undefined, style);
      // rocketcharts.addIndicator(id, params, series, panel);
      rocketcharts.addIndicator("movingaverageconvergancedivergance", undefined, 0);
 
   });
</script>

A View of the Architecture from 30,000 Feet

Before we jump right into how to use rocketcharts, let’s first understand what is going on behind the scenes. If you look at the code above, you’ll notice the first method call: rocketcharts.init(element, settings); “element” points to a container in your DOM, typically a DIV, where you want the chart displayed. Rocketcharts respects any styles you have set for this element and will not resize it; Instead, JQuery is employed to append a canvas, referred to as a chart panel, to that DIV.

“But what is a panel?” If you look again at the code block above, you’ll notice I don’t specify the last parameter of the “addIndicator” call which specifies a panel ID. The lack of a panel being passed creates a new panel for the indicator to be drawn on, i.e. a new canvas appended to the DOM. Adding a new element for each panel, rather than drawing all the panels on one large element, brings several advantages to the table. For one, we can leverage the existing power of a modern browser using JQueryUI to allow effortless user-resizing of the panels. No need to worry about vertical scrolling either, as this is taken care of for us.

Once we have our panels appended to the DOM, the real fun can begin: pixel manipulation. “Am I nerd, a geek or just a passionate developer? Playing with pixels is fun, dammit!” Essentially, rocketcharts performs some initial calculations, then loops through each panel and calls it’s draw method.

Panel-Drawing-Flow

The State of Rocketcharts

For the moment, rocketcharts is functionally quite basic. You can add your own Open-High-Low-Close-Date data to the chart. You can add one of three indicators and specify to draw over the primary series or on a new panel. Simple for now but the foundation is in place – adding additional studies/indicators is a simple affair.

Within the immediate future I plan to add zooming and horizontal scrolling, more indicators and a more robust UI allowing the user, rather than you, to manage the chart. I encourage you to follow along with the source, all feedback is welcome and will help keep the momentum of the project high.



Sep 12, 2011

Raster Text In HTML5's Canvas

I love HTML5′s canvas. Armed with only a modern browser, we now have access to a plethora of drawing/imaging functionality with no third-party plugins required. Unfortunately, life isn’t all sunshine quite yet, echoed across the web is one resounding cry: “drawText is performance murder.” Even more unfortunately, the project I am currently building requires heaps (pun intended) of drawText() calls.

So, what to do? How do we quickly get a block of text onto a canvas? The answer is to manipulate the pixels of the canvas directly. I feel that code speaks louder than words, so let’s jump right in. First, you’ll need an image that represents our font:

bitmapfont source

As you can see, I am using a bitmap font (no anti-aliasing) and I am outputting all characters in order, starting at charcode 33. Note: The lookup function we are going to write today depends entirely on these characters being in order. The next step is to load the font image into an off-screen buffer, or in our particular case, a hidden canvas element.

var fontImage = new Image();
fontImage.onload = function(){
  var bufferCanvas = document.getElementById("bufferCanvas");
  var bufferContext = bufferCanvas.getContext("2d");
 
  bufferContext.drawImage(fontImage, 0, 0, 566, 7);
};
fontImage.src = "bitmapfont.png";

Now that we have our image drawn into the canvas, we are able to extract a representative array of pixels and begin processing them. The idea is to iterate over each block (every 4 elements of the array correspond to red, green, blue and alpha channels of a single pixel) and record the significant points for each character.

var fontPoints = new Array();
var fontImage = new Image();
fontImage.onload = function(){
  var bufferCanvas = document.getElementById("bufferCanvas");
  var bufferContext = bufferCanvas.getContext("2d");

  bufferContext.drawImage(fontImage, 0, 0, 566, 7);

  var w = 566;
  var fontPixelArray = bufferContext.getImageData(0, 0, w, 7);

  // Store pointer directly to array of data to speed up lookups
  var fontPixelData = fontPixelArray.data;
  var total = -1;
  var x = 0;
  var y = 0;
  var index = -1;
  var pointsLength = -1;

  for (var i=0; i < 95; i++) {
    // Each array element is an array that stores relative x and y coordinates
    fontPoints[i] = new Array();
  }

  for (var i=0; i < fontPixelData.length; i++) {
    // Add up the R, G, B values
    total = fontPixelData[i] + fontPixelData[i+1] + fontPixelData[i+2];
    
    // If the total = 0 it's a black pixel, if not, we need to record it
    if (total > 0) {
      x = i / 4 % w;
      y = ( i / 4 - x) / w;

      // We can derive the character index by dividing by the character width
      index = Math.floor(x/6);

      x = x - (index * 6);

      pointsLength = fontPoints[index].length;

      fontPoints[index][pointsLength] = {x: x, y: y};
    }
    
    i += 3;
  }
};
fontImage.src = "bitmapfont.png";

As illustrated above, we first create an array containing 94 child arrays – each will hold the significant points of the associated glyph; next, we iterate over the CanvasPixelData array in chunks of 4, screening out black pixels. Once we come upon a non-black point, we derive it’s relative coordinates and character index, then add the point to the appropriate array.

At this point, we have an array of coordinates to reproduce every glyph in our original image. As I’m sure you have anticipated, we now need a method we can invoke that will imprint these coordinates onto an existing image. Again, code speaks louder than words:

function setPixel(imageData, x, y, r, g, b, a) {
    index = (parseInt(x) + parseInt(y) * imageData.width) * 4;
    imageData[index+0] = r;
    imageData[index+1] = g;
    imageData[index+2] = b;
    imageData[index+3] = a;
}
 
function rasterText(imageData, text, x, y) {
    var len = text.length;
    var i = 0;
    var code = 0;
    var characterPixelLength = -1;
    
    var startX = x;
    var startY = y;
    
    for (i=0; i < len; i++) {
        code = text.charCodeAt(i) - 33;
        
        if (code > -1) {
            characterPixelLength = fontPoints[code].length;
            
            for (var j=0; j < characterPixelLength; j++) {
                setPixel(imageData,
                        startX + fontPoints[code][j].x,
                        startY + fontPoints[code][j].y,
                        255, 255, 255, 0xFF);
            }
        }
        
        startX += 6;
    }
}

Our new “rasterText” method accepts a string as one of it’s parameters. For each character in this string, we lookup the number of points required to reproduce it, then for each of those points, we call the helper method “setPixel.” You’ll also notice on line 19, we determine the index in our array by subtracting 33 from the character code. A space resolves to -1, but you’ll notice we increase our “startX” outside that condition, so the space is preserved.

Here is how we would use this method from start to finish:

var destinationCanvas = document.getElementById("destinationCanvas");
var context = destinationCanvas.getContext("2d");
var imageData = context.getImageData(0, 0, context.width, context.height);
 
rasterText(imageData.data, "Hello World!", 10, 10);
 
context.putImageData(imageData, 0, 0);

Awesome! However, I’m sure upon reflection you’ll be identifying the limitations of this technique. Namely, you’ll need to load and pre-process a different image for each font face and font size. Up front costs in terms of your effort, sure, but during testing my aforementioned project, text rendering time was reduced by a mean ~20%.

In closing, the method we built here today is by no means a nail in the text-rendering-poor-performance coffin. What it is, however, is another tool in your arsenal - I urge you to treat it like a scalpel, not a hammer.



Aug 30, 2011

User Activity Tracking in ASP.NET MVC3 via Global Filters

When designing enterprise applications I often run into the same request from the business: ‘user tracking.’ Who created this entity? What time? Why was this entity created (origin tracking)? In the past I have seen colleagues create additional columns, such as userId and creationTime, on every ‘trackable’ table in the system. At the time, this made sense; However, with the advent of Global Filters in MVC3, more efficient ways of doing this have become apparent. Let’s jump in head first.

Open up Visual Studio 2010 and your MVC3 project. If you’d like, simply create a new MVC3 Web Application for the purposes of this tutorial. Next, create a folder under the project root named ‘Filters.’ In this folder, create a class named ‘UserActivityAttribute.cs’ – please note, these names can be anything you desire and hold no special meaning. When you’re finished, your solution explorer should look something like the following:

solution

Still with me? Awesome. Open up your newly created class and inherit from ‘ActionFilterAttribute.’ ActionFilterAttribute has several methods you can override which you can read about individually over at MSDN; For now though, let’s focus on ‘OnResultExecuting.’ As you might have guessed, this method is fired before the result is executed. Go ahead and override this method, bringing your class to look something like this:

public class UserActivityAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        base.OnResultExecuting(filterContext);
    }
}

Now for the fun stuff! As I’m sure you’ve observed, we get one argument from this method: ‘filterContext.’ From this ResultExecutingContext we will corral all the information we need to track our user’s actions. Which ActionFilterAttribute method to override depends on what type of information you are trying to capture and the same goes for the approach. To that end, let’s say that in your scenario entities are created using the standard ‘Create’ action. Furthermore, assume that after successful creation you redirect to that entities ‘Details’ action.

So, with these assumptions fresh in our minds, what is the first step to isolate the data we want?

public class UserActivityAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if ((filterContext.Result is RedirectToRouteResult) && 
            (filterContext.RequestContext.HttpContext.Request.RequestType == "POST"))
        {
 
        }
        base.OnResultExecuting(filterContext);
    }
}

As you can see, we check that the result primed for execution is of the type ‘RedirectToRouteResult.’ Additionally, we ensure the request was a POST, the default request type for ‘Create’ methods. Now that we have this in place, let’s extract some information about the RouteData of the Request and filter down further.

public class UserActivityAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if ((filterContext.Result is RedirectToRouteResult) &&
            (filterContext.RequestContext.HttpContext.Request.RequestType == "POST"))
        {
            var originController = filterContext.RouteData.Values["controller"].ToString();
            var originAction = filterContext.RouteData.Values["action"].ToString();
 
            if (originAction == "Create")
            {
 
            }
        }
        base.OnResultExecuting(filterContext);
    }
}

Now we have the controller and action of the request, using this information we filter out actions not matching our mock scenario, create calls. The final step is to decide what information to store and to format it accordingly. Let’s assume this application tracks brick-and-mortar stores and the customers that frequent them. We have two different controllers, Customer and Store, and based on what we are creating we want to change the formatting. I’ll let the code do the talking:

public class UserActivityAttribute : ActionFilterAttribute
{
  public override void OnResultExecuting(ResultExecutingContext filterContext)
  {
    if ((filterContext.Result is RedirectToRouteResult) &&
        (filterContext.RequestContext.HttpContext.Request.RequestType == "POST"))
    {
      var originController = filterContext.RouteData.Values["controller"].ToString();
      var originAction = filterContext.RouteData.Values["action"].ToString();
 
      if (originAction == "Create")
      {
        RedirectToRouteResult redirectResult = filterContext.Result as RedirectToRouteResult;
        var form = filterContext.RequestContext.HttpContext.Request.Form;
 
        var destinationController = redirectResult.RouteValues["controller"];
        var destinationAction = redirectResult.RouteValues["action"];
        var destinationId = redirectResult.RouteValues["id"];
 
        var destination = "/" + destinationController + "/" + destinationAction + "/" + destinationId;
        var title = "";
 
        switch (destinationController)
        {
          case "Customer":
            title = form["FirstName"] + " " + form["LastName"];
            break;
          case "Store":
            title = "#" + destinationId.ToString();
            break;
        }
 
        var action = "Created " + destinationController + " [" + title + "](" + destination + ")";
        Tracking.RecordSystemUserAction(action);
      }
    }
  }
}

Too cool, right? I love this stuff. Let’s break it down; First off, we pull out and cast the Result object. Secondly, because we know we are in a post, we pull out the form the user submitted. Thirdly, we extract all of the destination route information and use it to switch and further format. Finally, we bundle all of this up into a nice, human readable string and send it off to our utility method to be saved.

How you format this information is entirely up to you and your use case. As you may have figured out from the psuedo-markup I am generating, in my case I take anything inside the brackets and generate a url from the destination. When history is presented the end user, they see a friendly ‘wall post’ style list, allowing them to easily click through their history.

Ok, so I said that was the last step, I fibbed. Now we have this badass user tracking ActionFilterAttribute, how do we tell MVC3 to execute this code on every request? Under the project root in your Solution Explorer, find and open a file named ‘Global.asax.’ You’ll see a method titled ‘RegisterRoutes.’ Paste the following code above it.

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
  filters.Add(new UserActivityAttribute());
}

Now find the ‘Application_Start()’ method in the same file. Ensure that ‘RegisterGlobalFilters(GlobalFilters.Filters);’ is called within. That’s it. For real this time. When your MVC3 application starts, it will call this method; Behind the scenes, every action method is adorned with the attribute we just created.

The possibilities exposed by Global Filters, coupled with their ease of implementation, makes them an essential card up your sleeve. I’ll leave you with one friendly piece of advice: Remember that these overridden methods will be called with every single request. Make your initial condition as exclusive as possible to save cpu cycles.

Best of luck.