Can we embed a chat widget on Twitter? Yes, with few tricks and Pusher we can create a widget and chat with our Twitter friends.
Overview: qop qop qop
This is a short description of qop, a simple and unpretentious chat widget written in Ruby and Coffeescript.
It uses Pusher to send chat messages to our Twitter
friends, and it can be embedded on a Twitter page.
The technical part: Web services are provided with Sinatra. Authentication is done via Omniauth. Data models
are written in Datamapper, and Handlebars is used for templates. Finally a custom Sprockets process will glue
everything together. As easy as that!
The bookmarklet loads the widget from our server, and this is the code:
Note that it will be easy to create a Chrome Extension, just add a manifest.json and Chrome will do the rest. The
Loading, please wait
We load jQuery and wait until is ready to be used. We also need to load Pusher library, and finally load qop.
The App class doesn’t do much, just initialise Pusher with the right credentials, and creates the contact list.
You’re a bit too Pushy
Pusher provides a great websockets service and API, and we use websockets to send chat messages
to our twitter friends.
The browser sends AJAX requests to our server. Since the chat widget is loaded from twitter.com, we are
doing cross domain requests. CORS stands for Cross-Origin Resource Sharing, and it does what it says. Modern
browsers support CORS, and we only care about modern browsers. We could use JSONP, but that is really old school
(and CORS is more elegant).
The browser makes CORS requests to our server using jQuery:
We force jQuery to use cors, by setting a flag to true, we also need to pass xhrFields
and set withCredentials to true. It shouldn’t be needed, but just in case we also set crossDomain
to true. It is a bit redundant, but there have been cases it won’t work otherwise. If you know
any better way of doing this, please let me know.
The server needs to know we want to accept requests from twitter.com. Since the server is a Sinatra
application we use the rack-cors gem. (You want to include localhost for testing).
Each url corresponds to a service we provide to the chat widget.
My Friends are your Friends
Let’s see how to populate the contact list
The chat_session method returns a JSON object with user’s online @friends, @uid
(unique identifier provided by Twitter) and @nickname. Note, online means the user has been
authenticated with our server. (The JST variable is generated by Sprockets and it holds our
The initPresenceChannel function is called (via a trigger) if chat_session request is succesful.
If the user is online (authenticated) we subscribe the user to his own presence channel.
If the user is not online, we will show a signup link inside the widget, asking the user to authenticate.
Authentication is done via the omniauth-twitter gem.
Presence Channels with Pusher
Each user has got a personal presence channel. We use this channel to communicate events to the user.
For example, we use the presence channel to let a user when a friend joins or leaves
the chat. Pusher makes it really easy to know about these events via webhooks. Here’s the server code:
When the user occupies the channel, the user status will be set to online, otherwise the user is offline.
We use “cowboy” notifications to send these status changes, for each online friend we send a friend_status
notification. Having said that, the code is highly inefficient, since we shouldn’t do that, It would be better
to send notifications outside the http request cycle… Anyway…
Look Ma, I have a friend
At this point the user is authenticated and his presence channel is working. It’s time to show/hide friends
and to open a chat box when we click on their nickname.
Adding an online friend is easy, we check if the friend isn’t already shown, then we fill a template
with the relevant data, and we append this template to the contact list. To remove an offline
friend is even easier.
Now we need to create a chat box. Pusher is here to help us again. We bind the click event so that
when we click on the nickname the browser does an AJAXchat_request to our server.
If our friend is online we create a new channel. This channel holds the chat between the two users.
We use Pusher to trigger a create_chat event in the browsers.
The createChat creates a new Panel, and it allows us to chat with our friend.
The Panel is where we type our messages and chat with our friend. Each Panel has a pusher channel associated
with it, and this channel is used to send chat messages.
To create the Panel, we render the template and append a text area.
We also need to subscribe the users to the new channel.
Twitter has keyboard shortcuts, we need to disable them when the user is typing. After the user hits enter
we send the message to our server:
Eventually, we’ll also receive a message. Again, we render the message using handlebars template and append
the message to the Panel. We also need to scroll the content all the way up to the top, so that we can read
the message when it arrives.
There’s a lot more to say, we still have to see how to use Omniauth to authenticate the user, how to use
DelayedJob to fetch friends data in background, how to setup a custom Sprockets without Rails, and the database
relations and models used.