Hacking it Out: When the client time is wrong

Tuesday, August 15, 2017

The Scenario

We have a site that displays articles,gotten from an API call. Let’s suppose our Article model as returned from the API looks like this:

Article {
    id,
    name,
    body,
    author,
    tags,
    published_at
}

On the client side, we pass each property of the article through a render function which determines how the result will be displayed. For instance, on the home page, we might want to display only the first 200 characters of each article, so we could have a function like this:

function (body) {
    let blurb = body.substring(0, 200) + "...";
    return blurb;
}

For the published_at property, we want to display the difference between now and when the article was published (for instance, 2 hours, 20 minutes). So we have a function like this (I’m using the MomentJS library):

function (timePublished) {
    let duration = moment.duration(moment().diff(moment(timePublished)));
    return duration.humanize();
}

(In case you’re not familiar with Moment:

  • moment() creates a new Date object, set to the current time, while moment(timePublished) creates one set to the time_published
  • diff finds the difference between the two times, and duration wraps it in a Moment Duration object
  • humanize returns a human-readable time duration — our desired result)

The Problem

Some users have reported seeing negative times. Things like "-1 minute". The negative values reported are majorly in the range of -1 to -5 minutes.

The, um, Debugging

After trying to replicate the issue on our own PCs in vain, we discover the reason for this: the user’s time is wrong!

Just in case you can’t see it yet:

Say we have an article that was published at 5:33pm UTC. Say, the correct time right now in UTC is 5:35, but this user’s device (UTC too) has its time set to 5:30 (5 minutes behind). The article would show as being -3 minutes old (instead of 2 minutes old) because Moment is calculating 5:30 minus 5:33.

Of course, this means that all time differences shown on this user’s device are wrong, not just the negative ones. What to do?

The Solution

We could:

  • tell the user “Your time is wrong; please fix it.”
  • make an API call to some external service to get the current time
  • include the time difference in our API response (the Article object)
  • include the correct time in all our API responses (as an extra item)

I’d rather not go with the first option unless absolutely necessary because, well, I believe in causing minimal stress to the user.

The second option is also undesirable because it means adding the overhead of an additional API call, possibly multiplied by the number of articles present.

We could go with the third, but I don’t like it, because it would be “polluting” the API response. Think of it this way: you make an API call to get details about an article. What is returned should be the Article object, as described above.

In other words, everything in the Article should be directly related to the Article. Having the server calculate and give you the timeDifference would violate this, as the timeDifference is something which is relevant only to the client, and does not belong to the Article object.

Which brings us to the fourth option: modifying all our API responses to look something like this:

{
    "status": "success",
    "data" : {
        // -> the Article object
    },
    "time": "2017-08-19 04:00 GMT"
}

This is doable, but looks...out of place. A new developer joining the team might likely ask, “What’s that time property doing in all our responses?”, and then a senior dev would go “Come my son, let me tell you a story from many, many years ago…”

Well. we could go with that. Or we could look around a bit more.

Looking around…

Ay caramba!

If you take a look at the response from the server, you’ll likely see, among the headers, a special one called “Date”. Here’s how it looks on my console:

Image for post

This Date header contains the time on the server as at when the response was returned, complete with the timezone. With this simple header, we’ve unlocked the solution to our tale of woe.

Here’s the new implementation:

  • Wherever we make the API call and get a response, we store the value of the Date header:
// assuming we're using jquery:
$.ajax({
    "url": url,
    "data": request,
    "success": function (response, status, xhr) {
        window.currentTimeOnServer = xhr.getResponseHeader("Date");
        // do stuff with your response
    }
});

Here, we attach the currentTimeOnServer to the global window object to ensure that it’s accessible in a different file.

  • Update our render function to use the newly-minted currentTimeFromServer
function (timePublished) {
    let nowTime = window.currentTimeOnServer ? moment(window.currentTimeOnServer) : moment();
    let duration = moment.duration(nowTime.diff(moment(timePublished)));
}

One last caveat…

This might not work right away with some APIs, as xhr.getResponseHeader(“Date”) may return null, even though you can see the Date header in Dev Tools. To fix this, you would need to add this header to your API response:

Access-Control-Expose-Headers: Date (Or add a comma to whatever’s already there, then add Date)

You’ll want to be sure that you’re not running any security risks by exposing the time on your server, though.

SOLVED. 💃💃

If you have another idea(s) on how to solve this problem, please share in the comments. If you liked this article, please recommend…er, clap, or whatever Medium’s calling it these days. Share too. Thanks!

Powered By Swish