Jason Rowe

Be curious! Choose your own adventure.

ejabberd offline messages

The following is how I created a custom ejabberd module to POST offline messages to a web application. I should mention this is not production ready I’ve only just started working with Erlang and the XMPP server ejabberd. The main reason I’m experimenting with XMPP is to provide apple push notifications once a user closes an iPhone application. My day job is programming in C# so this is going to be from a Windows .Net developer perspective.

Why use ejabberd which is written in a wacky language called Erlang? Well… you will have to decide for yourself. Erlang is a functional programming language which is known for concurrency and high reliability. Also, it’s the 24th most popular language on GitHub as of writting this so it can’t be that bad. :)

If you don’t have ejabberd setup yet see my previous post on getting strophejs, ejabberd, and IIS all working together.

Step one, take a deep breath and get up to speed on XMPP, ejabberd, and Erlang. Easy right?
Don’t worry, it will all come together by the end of this post. Well, at least you will write some Erlang by the end of the post. Here are some resource to have handy.

Books:

  1. Learn You Some Erlang
  2. XMPP, the definitive guide

Let’s start with getting the Erlang environment up and running. You need Erlang to compile the new module. The Erlang version is important because it should match the one used in the installed ejabberd server. What version of Erlang was used with your installed ejabberd? You can find that information here using your ejabberd version number (if you don’t know the version number skip ahead to the next section before following this link): http://www.ejabberd.im/erlang.

If you don’t know your ejabberd version here is how to get it.

Go to start, run, and type cmd to open a command prompt. CD to your ejabberd\bin folder.
Mine in “D:\Program Files (x86)\ejabberd-2.1.9\bin”. Then run the following command:

ejabberdctl status

You should see something like this “The node ejabberd@localhost is started with status: started
ejabberd 2.1.9 is running in that node”

Note on ejabberd start error and erlang cookie

If you you get an error like the following “Failed RPC connection to the node ejabberd@localhost: nodedown”
Try to run the start command:

ejabberdctl start

If you get the same error when trying to start you might have a “.erlang.cookie” mismatch. On my box ejabberd runs using the
erlang cookie from “C:\Windows\.erlang.cookie”. This needs to match the one in your user folder. My Erlang cookie is located here:
“C:\Users\rowej\.erlang.cookie”. If you are getting the error mentioned, copy your erlang cookie from the windows folder
into your user folder.

Now that you have the ejabberd version go to ejabberd official site and see which version of erlang was used. This is important so you are using the same
version when you compile your module.

Now open your favorite text editor. I recommend Sublime Text 2 with Erlang syntax highlighting turned on. Also, there are several choices of plugins for NetBeans or Eclipse. The officially recommended IDE is Erlang mode for Emacs.

The next step is to get a basic module installed and make sure it is working. Lets start with a very simple module that does nothing but logs out information. Finally, time to start coding!

Create a file named mod_http_offline.erl.

Lets start with a very simple module that just does logging. This should give us a good idea of how ejabberd handles modules. Fill in the file you created “mod_http_offline.erl” with the following code:


%% name of module must match file name
-module(mod_http_offline).

-author("Earl The Squirrel").

%% Every ejabberd module implements the gen_mod behavior
%% The gen_mod behavior requires two functions: start/2 and stop/1
-behaviour(gen_mod).

%% public methods for this module
-export([start/2, stop/1, create_message/3]).

%% included for writing to ejabberd log file
-include("ejabberd.hrl").

%% ejabberd functions for JID manipulation called jlib.
-include("jlib.hrl").

start(_Host, _Opt) -> 
		post_offline_message("testFrom", "testTo", "testBody"),
		?INFO_MSG("mod_http_offline loading", []),
		ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 50).   



stop (_Host) -> 
		?INFO_MSG("stopping mod_http_offline", []),
		ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 50).



create_message(_From, _To, Packet) ->
		Type = xml:get_tag_attr_s("type", Packet),
		FromS = xml:get_tag_attr_s("from", Packet),
		ToS = xml:get_tag_attr_s("to", Packet),
		Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]),
		if (Type == "chat") ->
			post_offline_message(FromS, ToS, Body)
		end.



post_offline_message(From, To, Body) ->
		?INFO_MSG("Posting From ~p To ~p Body ~p~n",[From, To, Body]),
		?INFO_MSG("post request sent (not really yet)", []).

Now lets get the necessary include files to compile the code. You will have to go to the ejabberd site and download the src.

select “source code” from the drop down list. Then use a tool like TarTool to unzip it. Copy jlib.hrl and ejabberd.hrl into the same folder where where you created the module source mod_http_offline.erl

Now open the erlang shell (programs > Erlang), type in:

Remember you must use a slash (forward slash). If you cut and paste from windows explorer you will get back slashes and you won’t be able to change directories.

cd("path/to/where.you/saved/the-module").

Then compile by doing the following:

c(mod_http_offline).

You should see this result and a .beam file now created in the directory:

./mod_http_offline.erl:8: Warning: behaviour gen_mod undefined

Shut down ejabberd server and copy the resulting .beam file to the directory where all the other ejabberd .beam files are.
\ejabberd-2.1.9\lib\ejabberd-2.1.9\ebin

Configure ejabberd to enable this module via the ejabberd.cfg. Add it to the list of modules in the Module section.

{mod_http_offline, []}

Start up eJabberd.

After it starts lets examine the log files to see if our module loaded.
example\path\ejabberd-2.1.9\logs\ejabberd.log

You should see the following log entries:

=INFO REPORT==== 2011-12-30 14:55:27 ===
I(<0.36.0>:mod_http_offline:44) : Posting From "testFrom" To "testTo" Body "testBody"

=INFO REPORT==== 2011-12-30 14:55:27 ===
I(<0.36.0>:mod_http_offline:45) : post request sent (not really yet)

=INFO REPORT==== 2011-12-30 14:55:27 ===
I(<0.36.0>:mod_http_offline:21) : mod_http_offline loading

Now we can have fun and send some offline messages. Get two XMPP clients running logged in with two different users. I used my admin account created during the ejabberd setup and a new test account created via the ejabberd Web Admin. I used the agxXMPP client to send messages back and forth but you can use which ever XMPP client is easier for you. You can find a list of them on the official XMPP website client list. Send a few message to a user who is not connected (offline) and check your ejabberd.log file.(example\path\ejabberd-2.1.9\logs\ejabberd.log). Here is what mine looks like after sending a Hello world message.


=INFO REPORT==== 2011-12-30 13:31:25 ===
I(<0.493.0>:mod_http_offline:44) : Posting From [] To "[email protected]" Body "hello world"

Shut down ejabberd and setup a page to handle HTTP POST’s via Erlang. use your favorite web framework for the page it really doesn’t matter. If you are using MVC .Net it might look somethign like this.

 [HttpPost]
        public ActionResult Process()
        {
            var formValues = Request.Form;

            string from;
            string to;
            string body;

            if (!string.IsNullOrEmpty(formValues["From"]))
            {
                from = formValues["From"];
            }

            if (!string.IsNullOrEmpty(formValues["To"]))
            {
                to = formValues["To"];
            }

            if (!string.IsNullOrEmpty(formValues["Body"]))
            {
                body = formValues["Body"];
            }

Then from the Erlang console send a post to make sure everything is working with this command:

http:request(post, {“http://localhost/OfflineDemoWebhost/Message/Process”,[], “application/x-www-form-urlencoded”,”From=earl the squirrel&To=you&Body=Hello World”}, [], [])

If you see a response starting with this you are good to go.

{ok,{{"HTTP/1.1",200,"OK"},


Now lets change the module to send the post adding in a few more lines of code.

%% name of module must match file name
-module(mod_http_offline).

-author("Earl The Squirrel").

%% Every ejabberd module implements the gen_mod behavior
%% The gen_mod behavior requires two functions: start/2 and stop/1
-behaviour(gen_mod).

%% public methods for this module
-export([start/2, stop/1, create_message/3]).

%% included for writing to ejabberd log file
-include("ejabberd.hrl").

%% ejabberd functions for JID manipulation called jlib.
-include("jlib.hrl").

start(_Host, _Opt) -> 
		?INFO_MSG("mod_http_offline loading", []),
		inets:start(),
		?INFO_MSG("HTTP client started", []),
		post_offline_message("testFrom", "testTo", "testBody"),
		ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 50).   



stop (_Host) -> 
		?INFO_MSG("stopping mod_http_offline", []),
		ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 50).



create_message(_From, _To, Packet) ->
		Type = xml:get_tag_attr_s("type", Packet),
		FromS = xml:get_tag_attr_s("from", Packet),
		ToS = xml:get_tag_attr_s("to", Packet),
		Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]),
		if (Type == "chat") ->
			post_offline_message(FromS, ToS, Body)
		end.



post_offline_message(From, To, Body) ->
		?INFO_MSG("Posting From ~p To ~p Body ~p~n",[From, To, Body]),
		 http:request(post, {"http://localhost/OfflineDemoWebhost/Message/Process",[], 
		 "application/x-www-form-urlencoded",
		 lists:concat(["From=", From,"&To=", To,"&Body=", Body])}, [], []),
		?INFO_MSG("post request sent", []).

Shut down ejabberd again and recompile. Copy the beam file overitting the old version. Now start up the server and you should have offline messages posting to your web application.


Posted

in

,

by

Comments

17 responses to “ejabberd offline messages”

  1. Vinay Julme Avatar
    Vinay Julme

    Hi Jason,

    I have to put a offline_hook. Thanks to your post on offline hook , I was able to learn and start with code.

    I did as you suggested in the post. But i am having trouble compiling the code.

    At first i got the exception of logger. So i Commented every thing regarding the INFO.

    Then got the exception of “inets”. So commented that too. I know it maybe stupid. Actually i just wanted to compile it first. So that is why i did it.

    But then I started getting obselete error. Following is the error:
    crash reason: {undef,[{otp_internal,obsolete,[inets,start,0]},
    {erl_lint,deprecated_function,5},
    {erl_lint,check_remote_function,5},
    {erl_lint,expr,3},
    {erl_lint,exprs,3},
    {erl_lint,clause,3},
    {erl_lint,’-clauses/3-fun-0-‘,3},
    {lists,foldl,3}]}

    Can you please let me know on how to solve this issue? Or what would be best way to solve my problem.

    I just want an offline hook.

    I would be grateful for any help.

    Thanks and Regards

    Vinay Julme
    ***********

  2. Jason Avatar
    Jason

    It sounds like you are missing the needed include files. I do mention how to get those in the post.

    “Now lets get the necessary include files to compile the code. You will have to go to the ejabberd site and download the src.

    select “source code” from the drop down list. Then use a tool like TarTool to unzip it. Copy jlib.hrl and ejabberd.hrl into the same folder where where you created the module source mod_http_offline.erl”

    If you still have problems I would suggest the following book which helped me get my head around the basics.

    http://learnyousomeerlang.com/modules#compiling-the-code

  3. Alexander Avatar

    Hi Jason, I stumbled upon this post while trying to find out how to get ejabbered to send push notifications to an iOS app. We use ejabberd with MySQL and have a codeigniter backend for ejabberd, that will also post to apples JSON api. I feel like the PHP part won’t be a problem, however, the erlang part scares me a little. Could you send me an email, so we can discuss this properly? We’re probably going to have to hire some outside talent to help us on this!

  4. Evren Avatar

    Hi,

    How its catches only offline messages ? I couldn’t understand it :)

    It works, but how I can catch online messages too ? And how can I fix FromS value ? It is empty :)

  5. HuyHoang Avatar

    Hi Jason, thanks for the post. I have tried but i got a problem.

    Most parts are working, but the from(sender) attribute is missing in the intercepted offline message.

    Did I miss something or is it a bug (in offline message, from attribute is just missing)?

  6. Johan Vorster Avatar
    Johan Vorster

    Hi Jason, great article. Easy to read and follow, but I am having one issue though. It my hook is throwing an error. I am running ejabberd 2.1.11, so not sure if that makes a difference, but it is a little frustrating seeing that everyone else isn’t having the same issue.

    Here’s the error, hope you can help me with it:

    =ERROR REPORT==== 2013-02-21 15:34:50 ===
    E(:ejabberd_hooks:294) : {if_clause,
    [{mod_http_offline,create_message,3},
    {ejabberd_hooks,run1,3},
    {ejabberd_sm,route,3},
    {ejabberd_local,route,3},
    {ejabberd_router,route,3},
    {ejabberd_c2s,session_established2,2},
    {p1_fsm,handle_msg,10},
    {proc_lib,init_p_do_apply,3}]}
    running hook: {offline_message_hook,
    [{jid,”test”,”zspc-10″,”Spark 2.6.3″,”test”,”zspc-10″,
    “Spark 2.6.3″},
    {jid,”johan”,”zspc-10″,[],”johan”,”zspc-10″,[]},
    {xmlelement,”message”,
    [{“id”,”4z5S1-69″},{“to”,”johan@zspc-10″}],
    [{xmlelement,”x”,
    [{“xmlns”,”jabber:x:event”}],
    [{xmlelement,”composing”,[],[]}]}]}]}

    Thanks
    Johan

  7. Rel Avatar
    Rel

    Great article Jason. Thank you. :)

  8. Komal Avatar
    Komal

    Hi Jason, its really great article. It helped me to start with Servlets.
    Thank You. :)

  9. Tolgay Avatar
    Tolgay

    Hi

    Thank you for this.But When I try to compile this I am getting this errors:http://prntscr.com/5ju85c How can I resolve them ? I added jlib.hrl and ejabberd.hrl to folder.

  10. Josh Avatar
    Josh

    Tolgay,

    If you have not figured it out yet, you need to include logger.hrl as logging as been moved out of the ejabberd.hrl, also you will need to have the ns.hrl file in the same folder as well. Also for me I had to go and find the xml.hrl online. Once you have the other two files, and add logger.hrl, you should be good.

  11. Nuno Horta Avatar
    Nuno Horta

    Hi,

    I get exactly the same output:

    Warning: behaviour gen_mod undefined

    but I can’t find the beam file. Is it suppose to be in the same directory?

  12. Peter Ojambo Avatar
    Peter Ojambo

    Hello, My name is peter. I have a social network site and i have been using the normal javascript ajax chat i created manually. I also used this same format to create phone messenger app. But it was using normal sockets.
    so i have migrated to using ejabberd IM server and have developed the xmpp client messenger and it is working well with mysql module configurations.
    But now i have history chats for messages by users and want to maintain the message. So i want to transfer these messages to the new database table called archive under ejabberd.
    I have failed import the messages as they are very many in thousands.
    so if some one know a script i can use to do the migration of data to the new database that will be good.

  13. Nitin Avatar
    Nitin

    Need help to create a module that acknowledges , client message has been received by the server , though i tried it with mod_ack_stanza but didn’t work

  14. Arfan Mirza Avatar

    Here is little change for ejabberd v-16.01

    create_message(_From, _To, Packet) ->
    ?INFO_MSG(“create_message From ~p To ~p Packet ~p~n”,[_From, _To, Packet]),
    FromS = _From#jid.luser,
    ToS = _To#jid.luser,
    Body = xml:get_path_s(Packet, [{elem, list_to_binary(“body”)}, cdata]),
    post_offline_message(FromS, ToS, Body).

    post_offline_message(From, To, Body) ->
    ?INFO_MSG(“Posting From ~p To ~p Body ~p~n”,[From, To, Body]),
    Sep = “&”,
    Post = [
    “from=”, From, Sep,
    “to=”, To, Sep,
    “body=”, Body ],
    httpc:request(post, {“[URL]”,[],
    “application/x-www-form-urlencoded”,
    list_to_binary(Post)}, [], []).

    %%end

  15. Jason Avatar
    Jason

    Very cool, thanks for this.

  16. […] I couldn’t progress with this, I tried to follow the guide at https://jasonrowe.com/2011/12/30/ejabberd-offline-messages/ but hit the same erlang compile […]

  17. project igi 3 apunkagames Avatar

    I’m not sure why but I think its a linking issue.

Leave a Reply

Your email address will not be published. Required fields are marked *