I don't necessarily like how we are becoming as dependant on web services as we are. Perhaps I have some tinfoil hat syndrome symptoms, but putting total control in other people's hands doesn't seem right. Web services certainly have their place, and I will certainly continue to use them, but where possible and appropriate, it would be nice to take control again. The decision, in part, depends on expectations. Some things are meant to be public, or there is an expectation that certain actions might be public or not totally private, and in these scenarios, web services are more appropriate. Given this idea of when web services are appropriate, it seems to me that instant messaging is a place where the user should have a fair amount of control. The things I say in an instant messaging session are things that I expect to be between me and the other party or parties whom I know are involved. This is why I am becoming uncomfortable with Facebook chat. Comparatively, unlike with other uses of Facebook, I am not comfortable with my instant messages being data mined. As such, I wanted to see if there is a way where I could reduce my dependence on Facebook chat.
My idea is that I could create an alternative way for people to contact me. The alternative should be easy to use as to not burden the user attempting to contact me. It must to a degree, integrate with Facebook since that is where people are, and that is how people would often attempt to start an instant messaging session with me. Some Facebook integration would reduce the need for people to register entirely new accounts, and put up with the hassle of doing so. It also serves as a great way for me to authenticate who they are. If users were registering new accounts, I would have to take it on faith that they are who they say they are. By linking with Facebook I get the assurance that the person I am communicating with, is the person behind the Facebook account which I have grown to trust. Now, of course this creates the possibility that someone has been a fraud for many years, but, there is only so much I can do.
To accomplish this, I set out to create a web XMPP client which could use Facebook authentication which would then communicate with my XMPP instant messaging account. The rest of this post will detail many of the experiences I had. It will include much if not all of the code to make this happen, however the code will be altered at times for this post. The code isn't final and I don't consider it to be of production quality but it does get the job done, and for personal experiments, this is often good enough.
The diagram here is meant to give a general overview of what is going on. It isn't a super precise diagram, I only included the details that I thought were important. There are also certainly important aspects which I have chosen to entirely not include, like third-party libraries that serve as key components to the system. The numbers listed, do not necessarily indicate the order in which items are processed.
Actual movement between pages occurs here. If the user is not logged in they are redirected to Facebook.
User is sent back, index.php is reloaded but this time being logged in.
Creates user if does not exist.
Web browser moves to a new webpage. Also sends Facebook user ID.
chat.py instantiates a listener
When listener receives a message it writes in the chat log.
chat.py includes chat.js
When the message button on chat.py is pressed chat.js pushes the message to send.py so that it can sent.
Periodically chat.js checks with chatReader.py to see if there are new messages to display.
chatReader.py monitors the chat log to see if there is anything new.
Periodically chat.js tells pidAlive.py that the session is still open.
pidAlive.py makes note of the fact that the session is still alive.
chat.py includes style.css.
cron periodically run pidKiller.py
pidKiller.py checks with PID records to see the status of chat sessions.
Upstart tells fbReplyBot.py to run.
index.php
index.php is the landing page for the web application. This is the first page that people will reach when visiting the web application. My preference would have been to use Python for the entire application, but due to the fact that this part of the application requires access to the Facebook API it was easier for me to simply use PHP here. This was in part because I have worked with the PHP Facebook API before. I know there is the unofficial Python Facebook API and in fact I have used it before, and it works well, but not in a web setting. I am only thinking about it now, but there is also the Facebook Javascript API which perhaps could have been used. This is something to consider for a hypothetical version 2. But as I have mentioned a few times on this blog, I don't know Javascript, I just use it from time to time. The frequency that this happens make me think I should just learn the basics.
In the usual new user process, the user comes to the page and is redirected to a login page controlled by Facebook. The user is then redirected back to this page, however, this time they are logged in. Each and every person who uses this application becomes registered with my local Openfire XMPP server. The user is not aware of this fact. The user's Openfire account is linked with their Facebook account. If the Facebook account has not already been linked to an Openfire account, the link will be made, if so, this linking process will be skipped over. The final function of this script is to notify the user of the consequences of me using a self-signed SSL certificate. Ultimately, potentially scaring some people with the self-signed certificate is better than not using SSL and is better than me paying the money to get a proper certificate. This part of the program also showed me how to do some browser detection.
chat.py
This is really the main part of the application. It is the final destination for a user's web browser and where the chat session occurs. But of course this script connects to other scripts which in turn connect to even more scripts and so on and so forth.
The first thing the script does is receive information sent to it from index.php. It then deals with the handling of listener.py. Every chat session needs an instance of listener.py because this is how messages are received, it listens for messages that are sent to it, that is, specifically, it listens for messages that I send from my XMPP client back to the web client application (the system being described in this blog post). Though, perhaps the situation is better described by saying that every active user needs one instance of listener.py, if there are multiple windows open to the chat application, there is still a need for only one instance of listener.py. The second function this script performs is the handling of listener.py instances. It checks to see if any existing listener.py instances are active, and if not it starts one.
Every instance of listener.py is represented by Linux/the operating system by a Process ID, a PID. This application keeps track of the PID, which user created the PID, and whether the user still has chat windows open that require listener.py. It does this by creating a file in "./pids/" for each instance of listener.py using the PID as the filename. Within each of these files, labelled with the PID of the listener.py instance has a user ID on the first line, and a timestamp on the second line. With all this information I can know what instances of listener.py are active, who owns them, and whether it is time to close the instance. Knowing the ownership of the listener.py instances, or the ownership of the PIDs at this point is important because if the application finds that there is already an active listener.py instance for the user it will not create a new one.
Finally, the code below provides the necessary HTML for the chat window as well as providing some hidden form fields in order to facilitate the movement of data between other parts of the application.
chat.js
This is the Javascript file that accompanies chat.py. It runs several pieces of code periodically feeding information to and from chat.py and the chat window contained within chat.py and other parts of the application. It facilitates the sending of a message when a message is typed and the send button is clicked by taking what is in the new message box located on chat.py and sending it to send.py which will be explained later. This script also monitors the chat log and continuously updates the chat window in chat.py with the chat log's contents. The chat log contains the entire chat. Not only does it contain the entire chat, it contains everything that is inside the chat window in chat.py. In this case, the chat window is specifically referring to the portion of the screen where all previous messages are displayed. And it containing everything that is in the window, also means that it includes HTML. The chat log is not simply just text, but a mixture of text and HTML. The final function of chat.js is to inform the system that listener.py instances should stay alive. It does this by periodically calling pidAlive.py which updates the timestamp of the appropriate PID record located in "./pids/" as mentioned earlier.
send.py
The code in this file has been adapted from here. I have left many of the original comments intact. This script takes a message and sends it to my XMPP client (not the application being described in this blog post) and makes an entry in the chat log.
chatReader.py
This script will be called by chat.js. It reads the contents of the chat logs and outputs back their contents. It would technically be possible for chat.js to read the chat log directly, but this extra step is necessary for security purposes.
pidAlive.py
This interacts with the PID records stored under "./pids/" by updating their timestamp. It takes the contents of the PID record and re-writes them with the updated timestamp. This script is ran periodically by chat.js. Without this, listener.py instances would be killed by pidKiller.py even if the browser is still open with the chat session.
pidKiller.py
This is ran periodically by cron. It looks through all the files in "./pids/" which represent active instances of listener.py. If the timestamp in the PID record is too old, it kills the instance of listener.py associated with the PID and removes the PID record. It checks to make sure that the PID it is about to kill is actually associated with an instance of listener.py to make sure that other system processes are not inappropriately killed. If somehow there is a PID record of a non-existent PID, the PID record is still removed.
fbReplyBot.py
The code below is nearly a one-to-one duplicate of this. Originally the code received XMPP messages and sent what was received back to the sender. In this case, the code receives messages and then sends back a reply with instructions on how to use the application described in this blog post.
upstart script
This simply starts fbReplyBot.py when the operating system boots, or to be more precise, when the network card starts.
Other Things Learned
I learned a lot about Inkscape ("an Open Source vector graphics editor") in creating the flow diagram in this blog post. I've always struggled finding good diagramming tools for Linux. I have researched the topic plenty and have yet to find something that has met my needs. If I had to, I could resort to cloud based diagramming tools, but, that feels like cheating to me, an admission that the Linux ecosystem lacks in some way. This was basically my first time using Inkscape. It feels like a fairly promising diagramming tool. The big revelation I had with Inkscape allowing me to recognize it as a powerful diagraming tool, was discovering the cloning functionality. In Inkscape, you can clone something which you have drawn, making several duplicates from a master. Then you can modify the master, such as the colour and have the changes be reflected in the cloned children. This is a huge plus when you have created a bunch of objects then want to change the colour of them. That said, there was a quirky aspect of its functionality that almost caused me to lose confidence. When resizing children the border thickness is scaled along with the resizing, that is, when you made the cloned child smaller, the border got skinnier, and when you made the child larger, the border got larger, but I wanted the border width to stay the same. There is an option in Inkscape that promises just this. It says that it maintains the boarder thickness when the object is scaled and I figured it would work for everything. However, it turns out that it does not work for cloned objects. The general way I overcame this was by drawing the boarders separately, but still making use of duplicating capabilities so that it wasn't a completely manual process. Additionally, there was a strange aspect that an arrow head on a line would not naturally match the colour of the line. To make it match according to this you have to go to: Extensions > Modify Path > Color Markers to Match Stroke. I used this, and it works, but it did cause me some confusion as to why this doesn't happen by default.
Also, just as I was finishing up this post, wordpress did this to my sourcecode, adding in br's all over the place, new paragraphs, replacing characters with HTML encoding etc. This is the type of thing that just makes you want to give up or leave it looking bad. But, I went back and I think I corrected everything. If you see some random br's or 's anywhere this is why. It also drives home the point as to why many developers refrain from using visual designers, because of mess-ups like these.
Resources and Thanks
I wouldn't have been able to do all this without help from other people on the Internet. Below is a list of many of the resources that helped me in the creation of this application.
net tuts+ - How to Create A Simple Web-based Chat Application - this taught me the concept of how to create a section on a webpage, a box, that could have scrolling content in it that updates. It gave me the idea of including HTML in the chat log, and then simply displaying the content of the chat log into a scrollable area/div. If it weren't for this I would have attempted to creat additional elements on the page when each new message is created at either end of the conversation. Admittedly describing what I learned here was a bit difficult, and I'm not sure if I did a good job at it, but this was a useful resource!
SleekXMPP - Without this project my project may simply not exist. This allowed me to connect to my Openfire/XMPP server from Python. This really shows one of the strengths of object oriented programming. I was able to simply drop in, or include the functionality of SleekXMPP and suddenly I was able to connect to my Openfire/XMPP server, something I'm not sure I would have ever been able to do otherwise since it is my guess that the programming involved in doing so would be over my head.
Openfire - This is the XMPP chat server I am using. It plays a critical role in this application.
Below are two videos. The first is a demonstration of the chat application. The second is a demonstration of some of my new Inkscape skills. There is some strange glitches in the second demo, I suspect it is just because I am having some mouse issues in the virtual machine I was using.