Making Xamarin.iOS talk to Meteor

Two good friends are in the late stages of their new startup product Respondly which I've been using for a while to do email support for my apps.

Customer support for Twitter & Email Easily stay on top of your support with a single view of all your support tweets and email.

Be fast, be happy. Lightning fast user experience. Get into flow and stay there.

Great for individuals and teams
Share the workload around your team, or power through your tweets and emails solo.

In short, it's a support management tool designed to work with twitter and email, and work for a team of support people. It would be easy to see a small to medium-sized online team using it, for example - 3-5 people sharing the same twitter account, but also keeping consistent contact, so if you get a reply from "Bob" this time, you get one from him next time, too.

So far, I love the product - it makes it easy to keep track of who I'm talking to, and keeps it all in one place rather than mixing it in with my inbox. The only thing lacking is a mobile interface, as I generally have time to do support when I'm on the ferry.

Respondly is written in Meteor, which is a rich web framework sitting inside Node.JS. It does a great job of blurring the lines between your client and server, and uses its own DDP protocol, which is its way of calling methods and sending changes to a collection over the wire, without sending the whole object graph. With Meteor at both ends, it's a very polished and elegant solution.

Except, I wanted to write a client in C#.

There is a DDP client already around for C#, which I managed to get working using the full framework, but it relies on the dynamic datatype, which isn't available in Xamarin.iOS. It wasn't too hard to move that over to a Dictionary<string, object> for the sake of testing.

Once that was working, the only other change was to get the latest version of the SuperSocket.Client library down, and comment out one line in Core/TcpClientSession.cs:

#if !SILVERLIGHT
//Set keep alive
Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
#endif

I'm not sure if this is an iOS issue, a Xamarin iOS/mono/unix sockets issue, or something else. It does work without KeepAlive tho.

After that, you can happily call the DDP server, and get method calls and collection updates:

Server side:

if (Meteor.isServer) {
  
  Movies = new Meteor.Collection("movies");

  // Seed the movie database with a few movies
  Meteor.startup(function () {
      if (Movies.find().count() == 0) {
          Movies.insert({ title: "Star Wars", director: "Lucas", "rating":[{"stars":1},{"stars":2}]} });
          Movies.insert({ title: "Memento", director: "Nolan"});
          Movies.insert({ title: "King Kong", director: "Jackson" });
      }
  });

  Meteor.publish("allMovies", function () {
	  console.log("allMovies");
    	return Movies.find(); // everything
  });
  
  Meteor.methods({
	  helloWorld: function() {
		  console.log("helloWorld");
		  Movies.insert({title: "Hello from iOS", director: "Nic and Phil"});
		  return "Oh, hai";
	  }
  });

Client Side:

//subscriber is an instance of IDataSubscriber, which gets a callback
//when a change comes in
client = new DDPClient (subscriber);

// you can't use localhost in the simulator!
client.Connect("192.168.1.186:3000");
client.Subscribe("allMovies");

client.Call ("helloWorld");

With this, method calls (client.Call("helloWorld")) works fine. Subscriptions are another matter, and I suspect why the DDPClient.NET isn't complete.

Subscriptions work with three basic verbs:

  • Added: make a new record. The whole record is sent down
    {"msg":"added","collection":"movies","id":"u8PbKPjFdb9ARyuxQ","fields":{"title":"Star Wars","director":"Lucas","rating":[{"stars":1},{"stars":2}]}}

  • Changed: just the ID and the changed parts of the object graph are sent down (after change to director and ONE of the stars, not both)
    {"msg":"changed","collection":"movies","id":"u8PbKPjFdb9ARyuxQ","fields":{"director":"George","rating":[{"stars":1},{"stars":10}]}}

  • Removed: just the ID is sent down
    {"msg":"removed","collection":"movies","id":"u8PbKPjFdb9ARyuxQ"}

So to make this work, I'd need a local store of Json objects (by ID, I suspect), and way to patch them. Initially, I thought the changed message was sending down a deep diff, but it's actually sending down anything which has changed at the top level only. If you have fields which is fairly deep, like:

var m = Movies.find({title: "Star Wars"}).fetch()[0];

m.director = "Alan Smythie";
m.rating[1].stars = 10;
m.film.sound = "DTS";
m.cast[2].charactorname = "Lea";
		  
Movies.update(m._id, m);

Meteor will send down everything which has changed from the top down:

{
  "msg":"changed",
  "collection":"movies",
  "id":"eYQoE879M2de75WWC",
  "fields":
    {"director":"Alan Smythie",
     "rating":[{"stars":1},{"stars":10}],
     "film":{"size":"35mm","sound":"DTS"},
     "cast":[
       {"actor":"Harrison Ford","charactorname":"Han"},
       {"actor":"Mark Hammil","charactorname":"Luke"},
       {"actor":"Carrie Fisher","charactorname":"Lea"}]
    }
}

I find this to be quite awesome, because I can make a client for it quite easily, and kinda nasty, because it's sending down a lot of data which hasn't changed. Structuring your data is clearly important if you want to make sure that your packets are kept small.

To be clear, tho, I don't blame them for doing it. Doing a deep diff wouldn't be easy on either the client or server.

Getting this working properly might be a job for another weekend, and another blog post. I still have to write something to talk to SQLite and store the Json objects by id, and patch them when they come in as change requests. Using Zack Gramana's CouchBase Lite port might help a lot with a query engine over the top, as Couch is normally JSON based.

So, while it's possible to talk to a Meteor server from C#, without a proper and complete client, it's not going to be much use, as the collection publishing is a key part of Meteor - but its far from impossible.

In this case, we (well, they) have put the iOS client on the back burner, as they need to get the product out and stable more than they need a mobile client. Hopefully that will change in the future, as I'd like to use the mobile client as much as I'd like to write it.

As a side note, if you control both ends, using SignalR would get you most of the benefits. It doesn't, however, do the changeset management that Meteor does - but the Meteor style of changeset management doesn't look quite as difficult or complex as it did when I started looking at it!

Nic Wise

Nic Wise

Auckland, NZ