Thursday, August 7, 2014

ObjectiveDDP and Meteor 0.8.2 compatibility

We shipped our version 1 iPhone app using version 0.13 of ObjectiveDDP to talk to a live Meteor server. When Meteor changed their protocols in 0.8.2, compatibility with the iPhone app died for us; ironically not because of lack of backward compatibility, but rather, a bug in ObjectiveDDP, which the new Meteor code exposed.

Upgrading ObjectiveDDP versions can solve the problem for new iOS apps, but what about the ones already out there? The nature of the bug means that ObjectiveDDP won’t even connect to the server; hence, if we bumped revisions, we’d have no way to alert the old clients that they’d need to upgrade. They’d just see endless unavailability.
  
To solve the problem, we needed to patch the server to have it accept the existing clients. The bug was that the old ObjectiveDDP was neglecting to inform Meteor of which protocols it could use, so the fix was to force Meteor to assume the old protocol (‘pre1’) if the compatibility list wasn’t specified.

Here’s what worked for me...

In ~/.meteor/releases there’s a release.json file corresponding to each Meteor version. I wanted to get 0.8.2 patched, so took a look inside 0.8.2.release.json. There, there’s a unique identifier for the version of the livedata component it’s using. In this case, with the line:

    "less": "188c110be5",
    "livedata": "f9cf0125ce",
    "localstorage": "27cc42083e",

Armed with that number, I edited
~/.meteor/packages/livedata/(that number)/os/packages/livedata.js
Somewhere inside, there’s a _handleConnect function. In the case of 0.8.2, it’s around line 1997:

   _handleConnect: function (socket, msg) {
    var self = this;

    // The connect message must specify a version and an array of supported
    // versions, and it must claim to support what it is proposing.
    if (!(typeof (msg.version) === 'string' &&
          _.isArray(msg.support) &&
          _.all(msg.support, _.isString) &&
          _.contains(msg.support, msg.version))) {
      socket.send(stringifyDDP({msg: 'failed',
                                version: SUPPORTED_DDP_VERSIONS[0]}));
      socket.close();
      return;
    }
    ...

(Note that I’m omitting to show the numbered comments on the right hand side of each line. Safest to leave them there and patch around them, I found).

That empty line after the self declaration was a perfect opportunity to sneak this in:

    var self = this;
    if (!msg.support) msg.support = ['pre1'];
    // The connect message must specify a version and an array of supported
    ...

And… world saved for now, but it’s a shame this was necessary. With more foresight I should have had the old iOS app connect to a unique hostname, one that resolves to the same IP address as the website, knowing that I can always shuffle DNS entries around to accommodate any future protocol divergences.

No comments:

Post a Comment