Keep Your Friends Close and Your 3rd Parties Closer

As part of our viewer engagement tracking, we send back a umtime parameter (MDN: milliseconds since the epoch) to our tracking servers, http://.../?umtime=1375503164030&.... Timing allows us to tell a story around how viewers engage with our content, for example “Unfolded the card, watched a video for 34 seconds, shared it on Facebook…” This is native code, obtained via Date.now(); so there’s not much that can go wrong. Pretty straightforward, right? We thought so too…

At Sharethrough we load our JS directly on page for our mobile publishers. Read more here. This is part of a series of case studies of interesting things we’ve seen as a result.

Well, That Was Unexpected

We use Tattle as a monitoring tool (pulling from Graphite) and were alerted to elevated error rates from our tracking servers. The offending URLs looked like:

1
http://.../?umtime=Mon Oct 10 2000 13:55:36 -0700&...

We began looking for patterns in samples of our engagement data:

  • It was happening inconsistently.
  • It was happening across all devices.
  • It was happening across all forms of content.

We also log the publisher from which engagements originate via a ploc parameter. Examining the logs we noticed that ploc was similar for all of these engagements. On this particular publisher’s site:

1
2
> Date.now()
Mon Aug 05 2013 16:45:37 GMT-0700 (PDT)

…because:

1
2
> Date.now
function (){return new Date}

(╯°□°)╯︵ ┻━┻ !!!1

On any other site, as you would expect:

1
2
> Date.now
function now() { [native code] }

Our short-term fix:

1
((new Date()).getTime();

This doesn’t prevent our users from overloading getTime() but this would be a more egregious violation whereas one could understand the confusion of what Date.now() might return.

That Could Have Gone Better

Rather than having to debug these as they arise, it would be nice to know that a site passed something akin to a browser passing Acid3 but for JS global objects that you depend on? That would be a pretty intensive undertaking and one that likely wouldn’t bear as much fruit if we focus on the entire footprint. Chances are there are some objects that are more likely monkey patched than others.

Rather than boiling the ocean, what if we started by coming up with a way to easily capture regressions around likely culprits?

Boo!

Enter the amazing (and amazingly fast) PhantomJS, something we’ve used as a headless testing solution. Phantom has a JS API (in addition to Ruby, etc.) that you could create a test harness around.

Easy to install:

1
2
brew update
brew install phantomjs

Quick script to demonstrate how to execute code in the context of the page under test:

1
2
3
4
5
6
7
8
9
10
11
12
var url = 'http://www.REDACTED.com'; // Sorry, no public shaming on this blog :)

var page = require('webpage').create();

page.open(url, function (status) {
  var remoteDateNow = page.evaluate(function () {
    return Date.now();
  });
  console.log('[Date.now() LOCAL] => ' + Date.now());
  console.log('[Date.now() REMOTE] => ' + remoteDateNow);
  phantom.exit();
});

And the resulting output (note that PhantomJS outputs console lines to your console):

1
2
3
sfo-rslifka:/tmp$ phantomjs date.now.js
[Date.now() LOCAL] => 1375921353912
[Date.now() REMOTE] => Wed Aug 07 2013 17:22:33 GMT-0700 (PDT)

From here, it would be relatively straightforward to put together some assertions around your dependencies. Lots of interesting places to take it from here:

  • Rather than your own assertions, how about incorporating PhantomJS into Jasmine or Mocha?
  • When a user is signing up and specifies their domain, have them wait a tick while you verify their site is playing nicely.
  • How about a daily or weekly job that runs over all of your users and alerts you (and/or them) to an issue?

Plenty of options available to help you troubleshoot your 3rd Party JavaScript in the wild!