Wednesday, 12 February 2014

Ramblings of an IM-project

In case you haven't notice, Heroku seems like an awesome platform. They seem to offer Clojure & CouchDB for free, which means at least I can retry the old dream of mine: an instant messenger which doesn't suck. Last time I tried implementing that I had some troubles with the chosen platforms. Also, now that we've seen a few years without the prehistoric Live Messenger 2009, it might be a good time time to rewrite the specs for this dream-IM of mine. So, let's begin:

What must the IM be capable of

  • Sending messages from a peer to another in an acceptable time
  • Offline-spams have to be sent and received
  • Possible P2Ping files - but if I'll implement this, it won't be implemented performance in mind, and will be meant for sharing screenshots. Dropbox, and any other thing currently capable of doing this is lousy at it.
  • Message's fonts, colours, modifiers such as italics&bold, and the size of font used in the client must all be possible to change
  • Real group conversations won't be implemented at least right away. Possibly you could add multiple recipients, or make the client abstract the server's inability to do conversations away, but that doesn't concern us yet. I'm currently designing only the server.
  • The client should be scriptable - but only by the local user.
  • Inline styling should be possible with HTML
  • User's have changeable nicknames, personal messages, personal images (which have to be uploaded to the internet) and states. Quoting the last specs from the summer of '12, the states are
    1. ONLINE
    2. BUSY
    3. AWAY
    5. LUNCH
    7. REAL OFFLINE (ie. lack of any client polling)

How are the users handled? Does everyone just log into a server, and see everyone there, or is there some sort of friend-system? I'd say at first I add people to the server, allow them thus to authenticate, and then people add other people as friends, and only when they are accepted, are conversations possible. But I'll write the core messaging first and only when it works, I'll build this kind of stuff.

How do people authenticate? Damn if I know. If I don't find any nice authentication-system built (note to myself: this looks like a nice one) on clojure+couchdb, I'll probably replicate the Pröng's authentication system.

I'd love to use Pröng's user-table on this, but because I can't access the MySQL-db Pröng uses outside the - server, sharing that table with an app living in Heroku would be a hard one to do securely. Besides, if I used Pröng's user-table, and this thing gained any traction, it'd mean I had to open up the Pröng also for anyone not being an user.

The IM should also support signing in from multiple places, and every one of them should receive all the messages sent to that user when asking. All the messages sent to the user after their last signing off from the client must be received when they sign on from any client. They could write some sort of filter that guarantees they won't receive the stupidest offline-messages (for example, when one boots up a computer last used a year ago, they should be able to opt-out of receiving the messages whose age is more than a month).

How would these filters be set up then? Since this is my dream-IM, I'm writing it of course in Clojure, which means the client has access to (read-string) and his lovely companion (eval). That means the user can customise their client as much they want (and even open the NREPL-connection to it, for serious modifications), and writing messages would be an emacs-like experience. Want to copy a text file from the interweb to a message? Write '(slurp "")', press C-x C-e (customisable key-binding) and (if the replace is enabled) the function call is removed from the message, evalled with given params, and whatever it returns will be set to the message. Want to do maths? Write (* 33 (/ 44 (Math/sqrt 484))), press C-x C-e and that S-expr is replaced with the number "66.0". If the replace is disabled, the thing works the same, but leaves the s-expr as they were. Maybe non-replacing eval could be bound to different keys?

Message passing

The messages are simple data structures. They contain the sender's id, message, time-of-sending (for timezone-related things this will be the time of the server when it receives the message from the client, and this will by default be shown in the receiving client), receiver's id... and in the common case nothing else. User's font-settings can be queried from the server with the sender's id, Whenever user changes their font-prefs, the server will be notified and the new settings will be used only with the new messages. They don't apply retrospectively.

The machinery to route these messages from the sender to the client will be an interesting one. I think it would be best if every user had their own outbox, where the server-thread leaves the messages after receiving them with HTTP's PUT. In the server would exist threads blocking on read on every outbox, and once an outbox gets a message, thread reading it checks its intended recipient. If the recipient('s inbox) exists, the message would be popped from the outbox and pushed into the inbox. It it didn't exist, the sender would receive a "Recipient not found" - message into their own inbox, with SYSTEM marked as the sender.

I think the requirement for multiple clients online makes the client-polling the only real way of getting stuff from the inbox into the client. Clients poll every... fifteenth(?) second for the state of the inbox, and the server responds with a vector of messages (+ maybe the current font prefs for every sender, so they don't require their own queries) that the client can process with however deems fit. The default is to sort them by the timestamp, ask all the font preferences from the server, and print them to the conversation windows with the preferences applied. When the client polls, server finds all the messages that are not marked to be already sent for that particular client, sends them and marks them sent for that client.

In the beginning the in- and outboxes reside only in two refs in the memory. When the system is ready for production, I'll have added a system that sends immutable copies of both of these into the couchdb every once in a while, so that not too much of the messages will get dropped in case something bad happens to the server.

When should we expect results?

I'll probably set up a google code repo (or a github-repo, for Heroku requiring git I have no good reason to avoid the Github) when I have anything to show off, and paste the url at least to twitter. I'll paste it to here also if & when I'll write more about the project.

No comments:

Post a Comment