Problem
I recently had the fortune to work on an iOS application that included a feature to allow users to chat with each other. The decision was made to use ejabberd as the backend for the chat, with a native, SMS like, interface for the front end.
One of the challenges we had to overcome was sending push notifications to offline users when they received an incoming chat message. At the time, it didn't seem like there was a great solution to get messages to offline users directly from ejabberd, so we started doing a bit of research. The solution we came up with requires a some integration, but only between 60-70 lines of code.
Hypothesis
Because we were concurrently developing a rails app to provide the API for the iOS app, we decided the easiest solution would be to forward the offline messages from ejabberd to the rails app and integrate ZeroPush to handle the communication with Apple's push notification service.
We decided to see if we could get ejabberd to forward the messages for offline users. In my research, I came across a module for ejabberd called mod_offline_prowl that was originally intended to forward offline messages from ejabberd to Prowl. After digging into the mod_offline_prowl code, I saw that it could be adapted to do what I needed. There were two things I needed it to do a bit differently.
-
Easily configure the url where the data was being sent.
- Everything was regularly deployed to a staging environment and compiling ejabberd modules per environment seemed like a maintenance headache.
- Format the POST body with the data needed by the rails app
Solution
The basic idea behind ejabberd modules is that there are lifecylce events that occur, such as a user coming online, a user going offline, or receiving a message, and a module can subscribe to a particular event. When an interesting event happens the modules that are subscribed to that event get run.
The original mod_offline_prowl uses gen_mod:get_module_opt to retrieve certain options from ejabberd.cfg, so it seemed reasonable to use a similar approach to retrieve a url and access token for our purposes.
Based on the applications requirements, I came up with a minimal piece of configuration that we could use to set the details per environment. We would need an auth token to avoid bogus messages from being forwarded, and a url where the messages should be forwarded.
{mod_offline_post, [
{auth_token, "server-auth-token"},
{post_url, "http://localhost:3000/your/custom/url"}
]}
The module gets the properties from ejabberd.cfg using the following:
Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token, [] ),
PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url, [] )
Each time a message is sent to an offline user, an http request to the PostUrl is made using the Token, and the contents of To, From, and Body fields. We implemented a controller action that accepts the incoming post and hands it off to ZeroPush. The message makes a bunch of hops between applications, but in reality there is very little code which means fewer bugs, and a lower cost of maintenance.
The full mod_offline_post module along with two others can be found on my github profile.