├── OPENFIRE_SETUP.markdown
├── README.markdown
├── SERVER_SETUP.markdown
├── examples
├── basic-chat.html
├── basic.html
├── connection.html
├── script.js
└── style.css
└── jquery.xmpp.js
/OPENFIRE_SETUP.markdown:
--------------------------------------------------------------------------------
1 | OPENFIRE SERVER SETUP
2 | ==================================
3 |
4 | Openfire is a very powerfull and user friendly XMPP server. Use it with this jquery xmpp is very easy, just following this steps for the next 3 minutes
5 |
6 | Prerequisites
7 | -------------
8 | Apache web server installed with mod_proxy (installed by default)
9 | Java JRE
10 |
11 | Download and install
12 | ---------------------
13 | Go to http://www.igniterealtime.org/downloads/index.jsp and download the installer. If you are using Debian (or ubuntu) download the .deb installer and
14 | install it using dpgk -i
15 |
16 | Now start the server (if the installation did not init it)
17 | If you used the .deb package you have a script in /etc/init.d/ which can be used to start, restart and stop the openfire server.
18 | The installation is done via web using the port 9090. Open your web browser and go to http://yourhost:9090
19 | Follow the steps and your server is ready to use. You can create a new user and try to log in using pidgin (or any xmpp client)
20 |
21 |
22 | Configuration
23 | -----------
24 | Now we need to enable BOSH and http binding. The second its already enabled but take care about the port used by openfire.
25 | In the main menu, go to the tab "Server configuration" and enable BOSH. In the same page, you should also remeber the port used for http binding.
26 | In my case it is 7070 but usually is 5280. Anyway, you can take the port number you want. Just remeber it.
27 |
28 | Once we have finished configuring the server restart openfire to get some configuration values refreshed (if not, you will take error 500 when doing BOSH requests)
29 |
30 | At this point openfire is ready.
31 |
32 | The last step is configure mod_proxy of apache.
33 | Open the file proxy.conf (/etc/apache2/mods-available/proxy.conf in Debian/Ubuntu)
34 | Add the following lines
35 |
36 | ProxyPass /http-bind http://127.0.0.1:7070/http-bind/
37 | ProxyPassReverse /http-bind http://127.0.0.1:7070/http-bind/
38 |
39 | Please check the port. It must be the same port configured in openfire
40 |
41 | Now enable the apache modules
42 |
43 | a2enmod proxy
44 | a2enmod proxy_http
45 |
46 | And restart apache. All configuration is done!
47 |
48 | Manage Openfire
49 | ----------------
50 | Openfire has a very friendly user interface. You will not have problems with it
51 |
52 | Final words
53 | ------------
54 | You can use the basic examples provided to test the connection (for example connection.html). Openfire and apache could be in different severs, in this case you only need to edit the
55 | file proxy.conf and restart apache
56 |
57 | Resources
58 | =========
59 | [A Website Chat made easy with XMPP and BOSH](http://blog.wolfspelz.de/2010/09/website-chat-made-easy-with-xmpp-and.html)
60 | [Openfire Forum](http://community.igniterealtime.org/thread/49577)
61 |
62 |
63 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | jQuery XMPP
2 | ==================================
3 |
4 |
5 | If you have a chat application in your web page then you probably are using [XMPP](http://en.wikipedia.org/wiki/Extensible_Messaging_and_Presence_Protocol)
6 | There are some opensource and easy to use servers that support this protocol but not for javascript, where the actual XMPP tools are very weighty
7 | because no framework is used and they implement everything.
8 |
9 | My idea is use jQuery because ajax support and DOM manipulation is already done, then my work is only create a few functions.
10 |
11 |
12 |
13 | Description
14 | -----------
15 | This plugin allows connect to a XMPP server and also provides handlers for events (message, presence, iq).
16 | You should have a web server redirecting the BOSH connections to the XMPP server.
17 | This plugin only offers the communication so you are free to do you user interface as you want.
18 |
19 | At the moment only one instance of XMPP is supported. The authentication methods available are plain and digestMD5
20 |
21 |
22 |
23 | How to use
24 | ==========
25 |
26 |
27 | Initialization
28 | --------------
29 |
30 | You should provide to the plugin the jid, password and handlers. Just something like this:
31 |
32 | $.xmpp.connect({resource:"MyChat", jid:"user@domain.com", password:"qwerty", url:"http://myboshservice.com/http-bind"
33 | onDisconnect:function(){
34 | alert("Disconnected");
35 | },onConnect: function(){
36 | $.xmpp.setPresence(null);
37 | console.log("Connected");
38 | },
39 | onIq: function(iq){
40 | console.log(iq);
41 | },onMessage: function(message){
42 | console.log("New message of " + message.from + ": "+message.body);
43 | },onPresence: function(presence){
44 | console.log("New presence of " + presence.from + " is "+presence.show);
45 | },onError: function(error){
46 | console.log("Error: "+error);
47 | }
48 | });
49 |
50 |
51 | The options jid and password are required. Usually are used onMessage and onPresence handlers.
52 |
53 |
54 | The object sent to onMessage is:
55 | `{from:"user@server.com/resource", to:"receiver@aaa.com", body:"Ey!"}`
56 | If message.body is null it means that is a composing notification message (the other person is writing a message)
57 |
58 | The object sent to onPresence is:
59 | `{from:"user@server.com", to:"receiver@aaa.com", show:"away"}`
60 | The normal values for "show" are: away, dnd (do not disturb) and null (online)
61 |
62 |
63 | The object sent to onIQ is the full XMPP object. Such xmpp is just XML you can access to the data like you do with the DOM elements.
64 | For example, when you receive a text message the full message is something like this:
65 |
66 |
67 |
68 | TEXT
69 |
70 |
71 |
72 | Then, you can use "find" to get the contents o "attribute" to get the attributes. In this example, we can get the value of the tag something like this:
73 |
74 | value = $(iq).find("something").html();
75 |
76 |
77 |
78 | Sending commands
79 | ----------------
80 |
81 | Send a basic text message
82 | `$.xmpp.sendMessage({body: "Hey dude!", to:"someone@somewhere.com", resource:"MyChat"});`
83 | Resource parameer is optional and instead of this parameter you can add the resource to the "to" parameter (to:"someone@somewhere.com/MyChat"). Take care of not use resource parameter on initialization if you want to use the second way.
84 | This resource parameter overrides the resource value provided on initialization (if any).
85 |
86 | Send a more complex text message
87 |
88 | $.xmpp.sendMessage({body: "Hey dude!", to:"someone@somewhere.com", resource:"MyChat", otherAttr:"value"},
89 | "My custom error ",function(){ alert("Message sent!"); });
90 |
91 | This command will send the text message and the object specified in the second parameter. The final message will be something like this:
92 |
93 |
94 | Hey dude!My custom error
95 |
96 | The second (optional) parameter is useful for notice errors or extra information.
97 |
98 | Setting a presence
99 | `$.xmpp.setPresence(null);`
100 |
101 |
102 | Send a generic command
103 | `$.xmpp.sendCommand(rawCommand, callback);`
104 | The raw command is the xmpp command plain text. For example, send command uses this methos as:
105 | `$.xmpp.sendCommand("Hey dude! ", callback);`
106 |
107 | Using this method you can send any command like iq or your custom commands.
108 | Also, you can use it if you need to use a more complex sendMessage method (just generate the xml content and send it through sendCommand).
109 | Or you can use this method to provide more helpers (like sendMessage or setPresence) and improve the library!
110 |
111 |
112 |
113 | NOTE: Remember that after connect you should set presence to online (null) if you want be visible by your contacts.
114 |
115 | The common presence types are:
116 |
117 | * null (null value, not the string "null") to set the presence to online
118 |
119 | * offline
120 |
121 | * dnd (do not disturb)
122 |
123 | * away
124 |
125 | Handlers
126 | -----------
127 | By default only message, iq and presence commands are captured. If you are using you own message types (when using sendCommand method) you should capture it by modifying the `messageHandler` method
128 | For example you are using a custom command called "notification". In this case, you need to append following code to messageHandler
129 |
130 | $.each(response.find("notification"),function(i,element){
131 | try{
132 | var e = $(element);
133 | xmpp.onNotification({from: e.attr("from"), to: e.attr("to"), text: e.find("text").text()});
134 | }catch(e){}
135 | });
136 |
137 | Of course, you need to provide the onNotification event on initialization
138 |
139 | $.xmpp.connect({resource:"MyChat", jid:"user@domain.com", password:"qwerty", url:"http://myboshservice.com/http-bind"
140 | onConnect: function(){
141 | $.xmpp.setPresence(null);
142 | console.log("Connected");
143 | },
144 | onNotification: function(notification){
145 | console.log("My custom command received!");
146 | console.log(notification);
147 | },onMessage: function(message){
148 | console.log("New message of " + message.from + ": "+message.body);
149 | }
150 | });
151 |
152 | Resources
153 | =========
154 | [XMPP home page](http://xmpp.org/)
155 | [jQuery Plugin Page](http://plugins.jquery.com/project/xmpp-lib)
156 |
157 |
--------------------------------------------------------------------------------
/SERVER_SETUP.markdown:
--------------------------------------------------------------------------------
1 | BOSH SERVER SETUP
2 | ==================================
3 |
4 |
5 | We are going to setup a local XMPP server with BOSH. This is a server not a proxy, which means that you can only use local accounts (not gmail or facebook for example).
6 |
7 | Download and install
8 | ---------------------
9 | The software use is ejabberd, a free server available for linux and windows. I use debian so I just type aptitude install ejabber. Other option is download it from the
10 | project webpage. Once you have downladed and installed try to init the server. In my case /etc/init.d/ejabberd start
11 | Now go to you web browser and type http://localhost:5280/http-bind/
12 | If everything went right a test webpage should be loaded.
13 |
14 |
15 |
16 | Configuration
17 | -----------
18 | The configuration is very easy. Open the config file ejabberd.conf (in my case, this file is located at /etc/ejabberd).
19 | Now we are going to enable BOSH service. Add the module mod_http_bind
20 |
21 | {modules,
22 | [
23 | ...
24 | {mod_http_bind, []},
25 | ...
26 | ]},
27 |
28 | And now http_bind
29 |
30 | {listen,
31 | [
32 | ...
33 | {5280, ejabberd_http, [
34 | http_bind,
35 | http_poll,
36 | web_admin
37 | ]
38 | },
39 | ...
40 | ]},
41 |
42 | Now restart ejabberd and the BOSH service is ready!
43 |
44 | Manage Ejabberd
45 | ----------------
46 |
47 | Ejabberd can be manage with the command ejabberdctl
48 | For example, to add a new user
49 | sudo ejabberdctl register testUser localhost testPassword
50 | Check this command to view all available options
51 |
52 | Once you have create one user, go to the examples directory and open the file basic-chat.html.
53 | Edit the variable url and put the new url http://localhost:5280/http-bind/
54 | Now load basic-chat.html into your navigator and try to login, you should be able to do it.
55 | Remember that only local users can log in (only users you created with ejabberdctl). If you want
56 | to login with external users you will need a xmpp proxy or other xmpp server (openfire for example)
57 |
58 | Resources
59 | =========
60 | [Enable BOSH](https://git.process-one.net/ejabberd/mainline/blobs/raw/v2.1.11/doc/guide.html#modhttpbind)
61 |
62 |
63 |
--------------------------------------------------------------------------------
/examples/basic-chat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Basic connection
4 |
5 |
6 |
76 |
77 |
78 | This example just connect notify when connected and show the connected contacts.
79 |
80 | Jid (ej: maxpowel@gmail.com, alvaro.maxpowel@chat.facebook.com)
81 |
82 | Password
83 |
84 | Connect
85 | Disconnect
86 |
87 |
88 |
89 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/examples/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Basic chat
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Login
13 |
14 | Password
15 |
16 | Connect
17 | Disconnect
18 |
19 |
20 |
21 |
22 |
23 | Available
24 | Busy
25 | Away
26 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/connection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Basic connection
4 |
5 |
6 |
51 |
52 |
53 | This example just connect notify when connected and show the connected contacts.
54 |
55 | Jid (ej: maxpowel@gmail.com, alvaro.maxpowel@chat.facebook.com)
56 |
57 | Password
58 |
59 | Connect
60 | Disconnect
61 | Get roster
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/examples/script.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function(){
2 | $("#connectBut").click(function(){
3 | var server_name = "@localhost";
4 | var jid = $("#jid").val() + server_name;
5 | var password = $("#pass").val();
6 | var logContainer = $("#log");
7 | var contactList = $("#contacts");
8 |
9 | //An example of bosh server. This site is working but it can change or go down.
10 | //If you are going to have a production site, you must install your own BOSH server
11 | //var url ="http://bosh.metajack.im:5280/xmpp-httpbind";
12 | var url ="http://ec2-23-20-64-30.compute-1.amazonaws.com:5280/http-bind";
13 | $.xmpp.connect({url:url, jid: jid, password: password,
14 | onConnect: function(){
15 | logContainer.html("Connected");
16 | $.xmpp.setPresence(null);
17 | },
18 | onPresence: function(presence){
19 | var curId = presence.from.split('@')[0];
20 |
21 | $("#contacts li").each(function() {
22 | if ($(this).data('username') == curId) {
23 | $(this).remove()
24 | return false;
25 | }
26 |
27 | });
28 |
29 | var status_icon = "available_icon";
30 | switch (presence.show) {
31 | case "dnd": status_icon = "busy_icon";
32 | break;
33 |
34 | case "away": status_icon = "away_icon";
35 | break;
36 |
37 | default: status_icon = "available_icon";
38 | }
39 |
40 | var contact = $("
");
41 | contact.append(""+ curId +" ");
42 | contact.find("a").click(function(){
43 | var id = MD5.hexdigest(presence.from);
44 | var conversation = $("#"+id);
45 | if(conversation.length == 0)
46 | openChat({to:presence.from});
47 | });
48 | contactList.append(contact);
49 | },
50 | onDisconnect: function(){
51 | logContainer.html("Disconnected");
52 | },
53 | onMessage: function(message){
54 |
55 | var jid = message.from.split("/");
56 | var id = MD5.hexdigest(message.from);
57 | var conversation = $("#"+id);
58 | if(conversation.length == 0){
59 | openChat({to:message.from});
60 | }
61 | conversation = $("#"+id);
62 | //conversation.find(".conversation").append(""+ jid[0] +": "+ message.body +"
");
63 |
64 | if (message.body == null) {
65 | return;
66 | }
67 |
68 | var current_message = ""+ jid[0].split('@')[0] +": " + message.body +"
"
69 |
70 | conversation.find(".conversation").append(current_message);
71 | conversation.find(".conversation").prop('scrollTop', conversation.find('.conversation').prop("scrollHeight"));
72 |
73 | },onError:function(error){
74 | alert(error.error);
75 | }
76 | });
77 | });
78 |
79 | $("#disconnectBut").click(function(){
80 | $.xmpp.disconnect();
81 | });
82 |
83 | });
84 |
85 |
86 | function openChat(options){
87 | var id = MD5.hexdigest(options.to);
88 |
89 | // Chat box
90 | var chat_window = "";
91 |
92 | // Chat box header
93 | chat_window += "";
97 |
98 | // Chat box actual conversation
99 | chat_window += "
"
100 |
101 | // Chat box message box
102 | chat_window += "
Send
";
103 | chat_window += "
";
104 |
105 | var chat = $(chat_window);
106 | var input = chat.find("input");
107 | var sendBut = chat.find("button.btn_sendMsg");
108 | var conversation = chat.find(".conversation");
109 | sendBut.click(function(){
110 | $.xmpp.sendMessage({to:options.to, body: input.val()});
111 | //conversation.append(""+ $.xmpp.jid +": "+ input.val() +"
");
112 |
113 | var current_message = ""+ $.xmpp.jid.split('@')[0] +": " + input.val() +"
"
114 |
115 | conversation.append(current_message);
116 | conversation.prop('scrollTop', conversation.prop("scrollHeight"));
117 | input.val("");
118 | });
119 |
120 | if ($('#chatBox_group').length == 0) {
121 | var chatBox_group = "
"
122 | $(chatBox_group).css('position', 'absolute');
123 | $(chatBox_group).css('z-index', 1000);
124 | $(chatBox_group).css('top', $(window).height() - 222);
125 |
126 | $("body").append(chatBox_group);
127 | }
128 |
129 | //$(chat).css('position', 'absolute');
130 | //$(chat).css('z-index', 1000);
131 | //$(chat).css('top', $(window).height() - 220);
132 | $(chat).css('float', 'right');
133 | $(chat).css('margin-right', '10px');
134 |
135 | $("#chatBox_group").append(chat);
136 | $(chat).show();
137 | }
138 |
139 | $('.chatBox_minimise').on('click', function() {
140 |
141 | if ($(this).text() == '_') {
142 | var newtop = $(window).height() - 25;
143 |
144 | $(this).closest('.chatBox').animate({top: newtop});
145 | $(this).closest('.chatBox').find('.chatBox_body').hide();
146 |
147 | $(this).html('¯ ')
148 | } else {
149 | var newtop = $(window).height() - 220;
150 |
151 | $(this).closest('.chatBox').find('.chatBox_body').show();
152 | $(this).closest('.chatBox').animate({top: newtop});
153 |
154 | $(this).html('_ ')
155 | }
156 |
157 |
158 | });
159 |
160 | $('.chatBox_close').on('click', function() {
161 | $(this).closest('.chatBox').remove();
162 | });
163 |
164 | $('#chatBox_status').on('click', function() {
165 | $.xmpp.setPresence($(this).val());
166 | });
167 |
168 | $('#chatBox_search').on('keyup',function() {
169 | $('#contacts li').hide();
170 |
171 | var search_text = $(this).val().toLowerCase();
172 | if (search_text == "") {
173 | $('#contacts li').show();
174 | return;
175 | }
176 |
177 | $('#contacts li').each(function(key, value) {
178 | if ($(value).text().toLowerCase().search(search_text) > -1) {
179 | $(this).show();
180 | }
181 | });
182 | });
183 |
184 | $(".myCurMsg").on('keyup', function(event){
185 | if(event.keyCode == 13){
186 | $(".chatBox_curmsg .btn_sendMsg").click();
187 | }
188 | });
189 |
190 |
--------------------------------------------------------------------------------
/examples/style.css:
--------------------------------------------------------------------------------
1 | #roster {
2 | padding: 5px;
3 | width: 200px;
4 | }
5 |
6 | #chatBox_status {
7 | width: 209px;
8 | border: solid 1px silver;
9 | height: 30px;
10 | margin-left: -5px;
11 |
12 | background: #f9fcf7; /* Old browsers */
13 | background: -moz-linear-gradient(top, #f9fcf7 0%, #f5f9f0 100%); /* FF3.6+ */
14 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9fcf7), color-stop(100%,#f5f9f0)); /* Chrome,Safari4+ */
15 | background: -webkit-linear-gradient(top, #f9fcf7 0%,#f5f9f0 100%); /* Chrome10+,Safari5.1+ */
16 | background: -o-linear-gradient(top, #f9fcf7 0%,#f5f9f0 100%); /* Opera 11.10+ */
17 | background: -ms-linear-gradient(top, #f9fcf7 0%,#f5f9f0 100%); /* IE10+ */
18 | background: linear-gradient(to bottom, #f9fcf7 0%,#f5f9f0 100%); /* W3C */
19 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f9fcf7', endColorstr='#f5f9f0',GradientType=0 ); /* IE6-9 */
20 | }
21 |
22 | #chatBox_search {
23 | width: 209px;
24 | border: solid 1px silver;
25 | height: 30px;
26 | margin-left: -5px;
27 | }
28 |
29 | #contacts {
30 | list-style-type: none;
31 | margin-left: -46px;
32 | margin-right: -6px;
33 | padding-top: 0px;
34 | margin-top:0px;
35 | }
36 |
37 | #contacts li {
38 | border: solid 1px silver;
39 | padding: 6px;
40 | background: #f9fcf7; /* Old browsers */
41 | background: -moz-linear-gradient(top, #f9fcf7 0%, #f5f9f0 100%); /* FF3.6+ */
42 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9fcf7), color-stop(100%,#f5f9f0)); /* Chrome,Safari4+ */
43 | background: -webkit-linear-gradient(top, #f9fcf7 0%,#f5f9f0 100%); /* Chrome10+,Safari5.1+ */
44 | background: -o-linear-gradient(top, #f9fcf7 0%,#f5f9f0 100%); /* Opera 11.10+ */
45 | background: -ms-linear-gradient(top, #f9fcf7 0%,#f5f9f0 100%); /* IE10+ */
46 | background: linear-gradient(to bottom, #f9fcf7 0%,#f5f9f0 100%); /* W3C */
47 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f9fcf7', endColorstr='#f5f9f0',GradientType=0 ); /* IE6-9 */
48 | }
49 |
50 | #contacts li a {
51 | text-decoration: none;
52 | }
53 |
54 | .chatBox {
55 | border: solid 1px gray;
56 | width: 250px;
57 | border-radius: 5px;
58 | box-shadow: 2px 5px 5px silver;
59 | float: right;
60 | }
61 |
62 | .chatBox_header {
63 | float: left;
64 | }
65 |
66 | .chatBox_toolbar {
67 | float: right;
68 | margin-right: 10px;
69 | }
70 |
71 | .chatBox_header_wrapper {
72 | background: #45484d; /* Old browsers */
73 | background: -moz-linear-gradient(top, #45484d 0%, #000000 100%); /* FF3.6+ */
74 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#45484d), color-stop(100%,#000000)); /* Chrome,Safari4+ */
75 | background: -webkit-linear-gradient(top, #45484d 0%,#000000 100%); /* Chrome10+,Safari5.1+ */
76 | background: -o-linear-gradient(top, #45484d 0%,#000000 100%); /* Opera 11.10+ */
77 | background: -ms-linear-gradient(top, #45484d 0%,#000000 100%); /* IE10+ */
78 | background: linear-gradient(to bottom, #45484d 0%,#000000 100%); /* W3C */
79 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#45484d', endColorstr='#000000',GradientType=0 ); /* IE6-9 */
80 |
81 | color: #FFF;
82 |
83 | height: 22px;
84 | padding-top: 5px;
85 | padding-left: 5px;
86 | opacity: 0.95;
87 | -moz-opacity: 0.95;
88 | filter:alpha(opacity=95);
89 | }
90 |
91 | .conversation {
92 | height:150px;
93 | overflow: auto;
94 | color: #25282b;
95 | background-color: #FFF;
96 | opacity: 0.9;
97 | -moz-opacity: 0.9;
98 | filter:alpha(opacity=9);
99 | }
100 |
101 | .available_icon {
102 | border-radius: 50%;
103 | height:12px;
104 | width: 12px;
105 | background-color: green;
106 | float: left;
107 | margin-right: 5px;
108 | margin-top: 3px;
109 | }
110 |
111 | .busy_icon {
112 | border-radius: 50%;
113 | height:12px;
114 | width: 12px;
115 | background-color: red;
116 | float: left;
117 | margin-right: 5px;
118 | margin-top: 3px;
119 | }
120 |
121 | .away_icon {
122 | border-radius: 50%;
123 | height:12px;
124 | width: 12px;
125 | background-color: orange;
126 | float: left;
127 | margin-right: 5px;
128 | margin-top: 3px;
129 | }
130 |
131 | .myCurMsg {
132 | border: solid 1px silver;
133 | height: 30px;
134 | margin-bottom: 5px;
135 | margin: 5px;
136 | }
137 |
138 | .msgBlock {
139 | border-bottom: solid 1px silver;
140 | padding: 5px;
141 | }
142 |
143 | .chatter_name {
144 | font-weight: bold;
145 | }
146 |
147 | .chatBox_minimise a, .chatBox_close a{
148 | color: #FFF;
149 | text-decoration: none;
150 | }
151 |
--------------------------------------------------------------------------------
/jquery.xmpp.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jquery.xmpp.js
3 | *
4 | * Copyright 2011 Alvaro Garcia
5 | *
6 | * This program is free software; you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation; either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program; if not, write to the Free Software
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 | * MA 02110-1301, USA.
20 | */
21 |
22 | (function($) {
23 |
24 | // Intercept XHRs coming from IE8+ and send via XDomainRequest.
25 | $.ajaxTransport("+*", function( options, originalOptions, jqXHR ) {
26 |
27 | // If this is IE and XDomainRequest is supported.
28 | if(navigator.appVersion.indexOf("MSIE") != -1 && window.XDomainRequest) {
29 |
30 | var xdr;
31 |
32 | return {
33 |
34 | send: function( headers, completeCallback ) {
35 |
36 | // Use Microsoft XDR
37 | xdr = new XDomainRequest();
38 |
39 | // Open the remote URL.
40 | xdr.open("post", options.url);
41 |
42 | xdr.onload = function() {
43 | completeCallback(200, "success", [this.responseText]);
44 | };
45 |
46 | xdr.ontimeout = function(){
47 | completeCallback(408, "error", ["The request timed out."]);
48 | };
49 |
50 | xdr.onerror = function(errorText){
51 | completeCallback(404, "error: " + errorText, ["The requested resource could not be found."]);
52 | };
53 |
54 | // Submit the data to the site.
55 | xdr.send(options.data);
56 | },
57 | abort: function() {
58 | if(xdr)xdr.abort();
59 | }
60 | };
61 | }
62 | });
63 |
64 | $.xmpp ={
65 | rid:null,
66 | sid:null,
67 | jid:null,
68 | url: null,
69 | uri: null,
70 | listening: false,
71 | onMessage: null,
72 | onIq: null,
73 | onPresence: null,
74 | onError: null,
75 | connections: 0,
76 | resource: null,
77 | connected: false,
78 | checkNetorkErrors: false,
79 | wait: 60,
80 | inactivity: 60,
81 | _jsTimeout: null, //Used to save the javascript timeout
82 | _timeoutMilis: 500,
83 | __lastAjaxRequest: null,
84 |
85 | /**
86 | * Connect to the server
87 | * @params Object
88 | * {jid:"user@domain.com",
89 | * password:"qwerty",
90 | * resource:"Chat",
91 | * url:"/http-bind",
92 | * wait: 60,
93 | * inactivity: 60,
94 | * onDisconnect:function(){},
95 | * onConnect: function(data){},
96 | * onIq: function(iq){},
97 | * onMessage: function(message){},
98 | * onPresence: function(presence){}
99 | * onError: function(error, data){}
100 | * }
101 | */
102 | connect: function(options){
103 | this.rid = Math.round(Math.random()*Math.pow(10,10));
104 | this.jid = options.jid;
105 | var split = options.jid.split("@");
106 | var domain = split[1];
107 | var xmpp = this;
108 | if(options.url == null)
109 | this.url = '/http-bind'
110 | else
111 | this.url = options.url;
112 |
113 |
114 |
115 | if(options.checkNetorkErrors != null)
116 | this.checkNetorkErrors = options.checkNetorkErrors
117 | if(!isNaN(options.wait)){
118 | this.wait = options.wait;
119 | }
120 |
121 | this._timeoutMilis = xmpp.wait * 1000;
122 |
123 | if(!isNaN(options.inactivity)){
124 | this.inactivity = options.inactivity
125 | }
126 |
127 | this.uri = this.jid;
128 | if(options.resource == null)
129 | this.resource = "";
130 | else{
131 | this.resource = options.resource;
132 | this.uri += "/" + this.resource;
133 | }
134 |
135 | //Events
136 | this.onMessage = options.onMessage;
137 | this.onIq = options.onIq;
138 | this.onPresence = options.onPresence;
139 | this.onError = options.onError;
140 | this.onDisconnect = options.onDisconnect;
141 | this.onConnect = options.onConnect;
142 |
143 | //Init connection
144 | var msg = " ";
145 | $.post(this.url,msg,function(data){
146 | var response = $(xmpp.fixBody(data));
147 | xmpp.sid = response.attr("sid");
148 |
149 | if(response.find("mechanism:contains('PLAIN')").length){
150 | xmpp.loginPlain(options);
151 | }else if(response.find("mechanism:contains('DIGEST-MD5')").length){
152 | xmpp.loginDigestMD5(options);
153 | }else{
154 | if(xmpp.onError != null){
155 | xmpp.onError({error:"No auth method supported", data:data});
156 | }
157 |
158 | }
159 | }, 'text');
160 | },
161 |
162 | /**
163 | * Attach to existing session
164 | * @params Object
165 | * {jid:"",
166 | * sid:"",
167 | * rid:"",
168 | * resource:"",
169 | * url:"",
170 | * wait: 60,
171 | * onDisconnect:function(){},
172 | * onConnect: function(data){},
173 | * onIq: function(iq){},
174 | * onMessage: function(message){},
175 | * onPresence: function(presence){}
176 | * onError: function(error, data){}
177 | * }
178 | */
179 | attach: function(options){
180 | this.jid = options.jid;
181 | this.sid = options.sid;
182 | this.rid = options.rid;
183 |
184 | var xmpp = this;
185 | if(options.url == null)
186 | this.url = '/http-bind'
187 | else
188 | this.url = options.url;
189 |
190 | this.uri = this.jid;
191 | if(options.resource == null)
192 | this.resource = "";
193 | else{
194 | this.resource = options.resource;
195 | this.uri += "/" + this.resource;
196 | }
197 |
198 | //Events
199 | this.onMessage = options.onMessage;
200 | this.onIq = options.onIq;
201 | this.onPresence = options.onPresence;
202 | this.onError = options.onError;
203 | this.onDisconnect = options.onDisconnect;
204 | this.onConnect = options.onConnect;
205 |
206 | if(!isNaN(options.wait)){
207 | this.wait = options.wait
208 | }
209 |
210 | this._timeoutMilis = xmpp.wait * 1000;
211 |
212 | if(options.onConnect != null)
213 | xmpp.connected = true;
214 |
215 | options.onConnect();
216 | xmpp.listen();
217 | },
218 |
219 | /**
220 | * Disconnect from the server using synchronous Ajax
221 | * @params function callback
222 | */
223 | disconnectSync: function(callback){
224 | var xmpp = this;
225 | xmpp.rid = xmpp.rid + 1;
226 | this.listening = true;
227 | xmpp.connections = xmpp.connections + 1;
228 | var msg = " ";
229 | $.ajax({
230 | type: 'POST',
231 | url: this.url,
232 | data: msg,
233 | success: function(data){
234 | xmpp.connections = xmpp.connections - 1;
235 | xmpp.messageHandler(data);
236 | xmpp.listening = false;
237 | //Do not listen anymore!
238 | //Two callbacks
239 | if(callback != null)
240 | callback(data);
241 | if(xmpp.onDisconnect != null)
242 | xmpp.connected = false;
243 | xmpp.onDisconnect(data);
244 | },
245 | dataType: 'text',
246 | async:false
247 | });
248 | },
249 |
250 | /**
251 | * Disconnect from the server
252 | * @params function callback
253 | */
254 | disconnect: function(callback){
255 | var xmpp = this;
256 | xmpp.rid = xmpp.rid + 1;
257 | this.listening = true;
258 | xmpp.connections = xmpp.connections + 1;
259 | var msg = " ";
260 | $.post(this.url,msg,function(data){
261 | xmpp.connections = xmpp.connections - 1;
262 | xmpp.messageHandler(data);
263 | xmpp.listening = false;
264 | //Do not listen anymore!
265 |
266 | //Two callbacks
267 | if(callback != null)
268 | callback(data);
269 |
270 | if(xmpp.onDisconnect != null)
271 | xmpp.connected = false;
272 | xmpp.onDisconnect(data);
273 |
274 | }, 'text');
275 | },
276 | /**
277 | * Do a MD5 Digest authentication
278 | */
279 | loginDigestMD5: function(options){
280 | var xmpp = this;
281 | this.rid++;
282 | var msg = " ";
283 | $.post(this.url,msg,function(data){
284 | var response = $(data);
285 |
286 | var split = options.jid.split("@");
287 | var domain = split[1];
288 | var username = split[0];
289 |
290 | //Code bases on Strophe
291 | var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/;
292 |
293 | var challenge = Base64.decode(response.text());
294 |
295 | var cnonce = MD5.hexdigest("" + (Math.random() * 1234567890));
296 | var realm = "";
297 | var host = null;
298 | var nonce = "";
299 | var qop = "";
300 | var matches;
301 |
302 | while (challenge.match(attribMatch)) {
303 | matches = challenge.match(attribMatch);
304 | challenge = challenge.replace(matches[0], "");
305 | matches[2] = matches[2].replace(/^"(.+)"$/, "$1");
306 | switch (matches[1]) {
307 | case "realm":
308 | realm = matches[2];
309 | break;
310 | case "nonce":
311 | nonce = matches[2];
312 | break;
313 | case "qop":
314 | qop = matches[2];
315 | break;
316 | case "host":
317 | host = matches[2];
318 | break;
319 | }
320 | }
321 |
322 | var digest_uri = "xmpp/" + domain;
323 | if (host !== null) {
324 | digest_uri = digest_uri + "/" + host;
325 | }
326 |
327 | var A1 = MD5.hash(username + ":" + realm + ":" + options.password) +
328 | ":" + nonce + ":" + cnonce;
329 |
330 | var A2 = 'AUTHENTICATE:' + digest_uri;
331 |
332 | var responseText = "";
333 | responseText += 'username=' + xmpp._quote(username) + ',';
334 | responseText += 'realm=' + xmpp._quote(realm) + ',';
335 | responseText += 'nonce=' + xmpp._quote(nonce) + ',';
336 | responseText += 'cnonce=' + xmpp._quote(cnonce) + ',';
337 | responseText += 'nc="00000001",';
338 | responseText += 'qop="auth",';
339 | responseText += 'digest-uri=' + xmpp._quote(digest_uri) + ',';
340 | responseText += 'response=' + xmpp._quote(
341 | MD5.hexdigest(MD5.hexdigest(A1) + ":" +
342 | nonce + ":00000001:" +
343 | cnonce + ":auth:" +
344 | MD5.hexdigest(A2))) + ',';
345 | responseText += 'charset="utf-8"';
346 |
347 | xmpp.rid++;
348 | var msg =""+Base64.encode(responseText)+" ";
349 | $.post(this.url,msg,function(data){
350 | var response = $(xmpp.fixBody(data));
351 | if(!response.find("failure").length){
352 | xmpp.rid++;
353 | var msg =" ";
354 | $.post(this.url,msg,function(data){
355 | var response = $(xmpp.fixBody(data));
356 | if(response.find("success").length){
357 | xmpp.rid++;
358 | var msg =" ";
359 | $.post(this.url,msg,function(data){
360 | xmpp.rid++;
361 | var msg ="" + xmpp.resource +" ";
362 | $.post(this.url,msg,function(data){
363 | xmpp.rid++;
364 | var msg = " ";
365 | $.post(this.url,msg,function(data){
366 | if(options.onConnect != null)
367 | xmpp.connected = true;
368 |
369 | options.onConnect(data);
370 | xmpp.listen();
371 | }, 'text');
372 | }, 'text');
373 | }, 'text');
374 | }else{
375 | if(xmpp.onError != null)
376 | xmpp.onError({error: "Invalid credentials", data:data});
377 | }
378 | }, 'text');
379 | }else{
380 | if(xmpp.onError != null)
381 | xmpp.onError({error: "Invalid credentials", data:data});
382 | }
383 | }, 'text');
384 |
385 | }, 'text');
386 | },
387 |
388 | /**
389 | * Returns the quoted string
390 | * @prams string
391 | * @return quoted string
392 | */
393 | _quote: function(string){
394 | return '"'+string+'"';
395 | },
396 | /**
397 | * Do a plain authentication
398 | */
399 | loginPlain: function(options){
400 | this.rid++;
401 | var split = options.jid.split("@");
402 | var user = split[0];
403 | var domain = split[1];
404 | var xmpp = this;
405 | var text = ""+Base64.encode(this.jid+"\u0000"+user+"\u0000"+options.password)+" ";
406 | var url = this.url;
407 | $.post(this.url,text,function(data){
408 | var response = $(xmpp.fixBody(data));
409 | if(response.find("success").length){
410 | xmpp.rid++;
411 | text ="" + xmpp.resource +" ";
416 | $.post(url,text,function(data){
417 | //xmpp.messageHandler(data);
418 | xmpp.rid++;
419 | text = " ";
420 | $.post(url,text,function(data){
421 | if(options.onConnect != null)
422 | xmpp.connected = true;
423 |
424 | options.onConnect(data);
425 | xmpp.listen();
426 | }, 'text');
427 | }, 'text');
428 | }, 'text');
429 | }else{
430 | if(options.onError != null)
431 | options.onError({error: "Invalid credentials", data:data});
432 | }
433 | }, 'text');
434 | },
435 |
436 | /**
437 | * Disconnected cause a network problem
438 | */
439 | __networkError: function(){
440 | //Notify the errors and change the state to disconnected
441 | if($.xmpp.onError != null){
442 | $.xmpp.onError({error:"Network error"})
443 | }
444 |
445 | if($.xmpp.onDisconnect != null){
446 | $.xmpp.onDisconnect()
447 | }
448 |
449 | $.xmpp.__lastAjaxRequest.abort();
450 | $.xmpp.connections = $.xmpp.connections - 1;
451 | $.xmpp.listening = false;
452 | $.xmpp.connected = false
453 |
454 | },
455 |
456 | /**
457 | * Wait for a new event
458 | */
459 | listen: function(){
460 | var xmpp = this;
461 | if(!this.listening){
462 | this.listening = true;
463 | xmpp = this;
464 | if(xmpp.connections === 0) {
465 | //To detect networks problems
466 | if(xmpp.checkNetorkErrors){
467 | clearTimeout(xmpp._jsTimeout);
468 | xmpp._jsTimeout = setTimeout(xmpp.__networkError,xmpp._timeoutMilis);
469 | }
470 | //
471 | this.rid = this.rid+1;
472 | xmpp.connections = xmpp.connections + 1;
473 | xmpp.__lastAjaxRequest = $.ajax({
474 | type: "POST",
475 | url: this.url,
476 | data: "",
477 | success: function(data){
478 | xmpp.connections = xmpp.connections - 1;
479 | xmpp.listening = false;
480 | var body = $(xmpp.fixBody(data));
481 | //When timeout the connections are 0
482 | //When listener is aborted because you send message (or something)
483 | // the body children are 0 but connections are > 0
484 | if(body.children().length > 0) {
485 | xmpp.messageHandler(data);
486 | }
487 | if ( xmpp.connections === 0 ) {
488 | xmpp.listen();
489 | }
490 | },
491 | error: function(XMLHttpRequest, textStatus, errorThrown) {
492 | if(xmpp.onError != null){
493 | xmpp.onError({error: errorThrown, data:textStatus});
494 | }
495 | },
496 | dataType: 'text'
497 | });
498 | }
499 | }
500 | },
501 |
502 | /**
503 | * Send a raw command
504 | * @params String Raw command as plain text
505 | * @params callback function callback
506 | */
507 | sendCommand: function(rawCommand, callback){
508 | var self = this;
509 |
510 | this.rid = this.rid + 1;
511 | this.listening = true;
512 | this.connections = this.connections + 1;
513 | var command = ""+ rawCommand+"";
514 |
515 | $.post(self.url,command,function(data){
516 | self.connections = self.connections - 1;
517 | self.messageHandler(data);
518 | self.listening = false;
519 | self.listen();
520 | if(callback != null)
521 | callback(data);
522 | }, 'text');
523 | },
524 |
525 | /**
526 | * Send a text message
527 | * @params Object
528 | * {body: "Hey dude!",
529 | * to: "someone@somewhere.com"
530 | * resource: "Chat",
531 | * otherAttr: "value"
532 | * }
533 | * @params data: Extra information such errors
534 | * @params callback: function(){}
535 | */
536 | sendMessage: function(options, data, callback){
537 | var toJid = options.to;
538 | var body = options.body;
539 |
540 | if(options.resource != null)
541 | toJid = toJid+"/"+options.resource;
542 | else if(this.resource != "")
543 | toJid = toJid+"/"+this.resource;
544 |
545 | //Remove used paramteres
546 | delete options.to;
547 | delete options.body;
548 | delete options.resource;
549 |
550 | //Other data
551 | var dataObj = $("");
552 | dataObj.append(data);
553 |
554 | //Add all parameters to the message
555 | var messageObj = $("bodyCont ");
556 | messageObj.find("message").attr(options);
557 |
558 | //Use raw text because jquery "destroy" the body tag
559 | var message = messageObj.html().split("bodyCont");
560 |
561 | var msg = message[0]+""+body+""+dataObj.html()+"";
562 | this.sendCommand(msg,callback);
563 | },
564 |
565 | /**
566 | * Change the presence
567 | * @params String The common presences are: null, away, dnd
568 | * @params callback: function(){}
569 | */
570 | setPresence: function(type, callback){
571 | var msg;
572 | if(type == null)
573 | msg = " ";
574 | else
575 | msg = ""+type+" ";
576 | this.sendCommand(msg,callback);
577 | },
578 |
579 | /**
580 | * Get if you are connected
581 | */
582 | isConnected: function(){
583 | return this.connected;
584 | },
585 |
586 | /**
587 | * Do a roster request
588 | */
589 | getRoster: function(callback){
590 | //Some XMPP server send a blank list. Use onIq in these cases
591 | var msg = " ";
592 | this.sendCommand(msg,function(data){
593 | var roster = [];
594 | $.each($(data).find("item"), function(i,item){
595 | var jItem = $(item);
596 | roster.push({name: jItem.attr("name"), subscription: jItem.attr("subscription"), jid: jItem.attr("jid")});
597 | });
598 | if(callback)
599 | callback(roster);
600 | });
601 | },
602 |
603 | /**
604 | * Get who is online
605 | * When presence it received the event onPresence is triggered
606 | */
607 | getPresence: function(){
608 | var msg = " ";
609 | var self = this;
610 | this.sendCommand(msg,function(data){
611 | self.messageHandler(data,self)
612 | });
613 | },
614 |
615 | messageHandler: function(data, context){
616 | var xmpp = this;
617 | var response = $(xmpp.fixBody(data));
618 |
619 | $.each(response.find("message"),function(i,element){
620 | try{
621 | var e = $(element);
622 | xmpp.onMessage({from: e.attr("from"), body: e.find(".body").html(),attributes:e[0].attributes,data:response.children()});
623 | }catch(e){}
624 | });
625 |
626 | $.each(response.find("iq"),function(i,element){
627 | if(xmpp.onIq){
628 | var e = $(element);
629 | if(e.find('ping').length==1){
630 | xmpp.handlePing(e);
631 | }
632 | else{
633 | xmpp.onIq(element);
634 | }
635 | }
636 | });
637 |
638 | $.each(response.find("presence"),function(i,element){
639 | try{
640 | var e = $(element);
641 | var status;
642 | if(e.attr("type") != null){
643 | status = e.attr("type")
644 | }else{
645 | status = e.find("show").html()
646 | }
647 | xmpp.onPresence({from: e.attr("from"), to: e.attr("to"), show: status});
648 | }catch(e){}
649 | });
650 | },
651 |
652 | /**
653 | * Replaces tags because jquery does not "parse" this tag
654 | * @params String
655 | * @return String
656 | */
657 | fixBody: function(html){
658 | html = html.replace(/<\/body>/ig, "")
659 | html = html.replace(/ ");
674 | }
675 | }
676 | })(jQuery);
677 |
678 | //Dependencies, you can use an external file
679 | // This code was written by Tyler Akins and has been placed in the
680 | // public domain. It would be nice if you left this header intact.
681 | // Base64 code from Tyler Akins -- http://rumkin.com
682 | var Base64 = (function () {
683 | var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
684 |
685 | var obj = {
686 | /**
687 | * Encodes a string in base64
688 | * @param {String} input The string to encode in base64.
689 | */
690 | encode: function (input) {
691 | var output = "";
692 | var chr1, chr2, chr3;
693 | var enc1, enc2, enc3, enc4;
694 | var i = 0;
695 |
696 | do {
697 | chr1 = input.charCodeAt(i++);
698 | chr2 = input.charCodeAt(i++);
699 | chr3 = input.charCodeAt(i++);
700 |
701 | enc1 = chr1 >> 2;
702 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
703 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
704 | enc4 = chr3 & 63;
705 |
706 | if (isNaN(chr2)) {
707 | enc3 = enc4 = 64;
708 | } else if (isNaN(chr3)) {
709 | enc4 = 64;
710 | }
711 |
712 | output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
713 | keyStr.charAt(enc3) + keyStr.charAt(enc4);
714 | } while (i < input.length);
715 |
716 | return output;
717 | },
718 |
719 | /**
720 | * Decodes a base64 string.
721 | * @param {String} input The string to decode.
722 | */
723 | decode: function (input) {
724 | var output = "";
725 | var chr1, chr2, chr3;
726 | var enc1, enc2, enc3, enc4;
727 | var i = 0;
728 |
729 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
730 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
731 |
732 | do {
733 | enc1 = keyStr.indexOf(input.charAt(i++));
734 | enc2 = keyStr.indexOf(input.charAt(i++));
735 | enc3 = keyStr.indexOf(input.charAt(i++));
736 | enc4 = keyStr.indexOf(input.charAt(i++));
737 |
738 | chr1 = (enc1 << 2) | (enc2 >> 4);
739 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
740 | chr3 = ((enc3 & 3) << 6) | enc4;
741 |
742 | output = output + String.fromCharCode(chr1);
743 |
744 | if (enc3 != 64) {
745 | output = output + String.fromCharCode(chr2);
746 | }
747 | if (enc4 != 64) {
748 | output = output + String.fromCharCode(chr3);
749 | }
750 | } while (i < input.length);
751 |
752 | return output;
753 | }
754 | };
755 |
756 | return obj;
757 | })();
758 |
759 |
760 | /*
761 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
762 | * Digest Algorithm, as defined in RFC 1321.
763 | * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
764 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
765 | * Distributed under the BSD License
766 | * See http://pajhome.org.uk/crypt/md5 for more info.
767 | */
768 |
769 | var MD5 = (function () {
770 | /*
771 | * Configurable variables. You may need to tweak these to be compatible with
772 | * the server-side, but the defaults work in most cases.
773 | */
774 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
775 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
776 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
777 |
778 | /*
779 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally
780 | * to work around bugs in some JS interpreters.
781 | */
782 | var safe_add = function (x, y) {
783 | var lsw = (x & 0xFFFF) + (y & 0xFFFF);
784 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
785 | return (msw << 16) | (lsw & 0xFFFF);
786 | };
787 |
788 | /*
789 | * Bitwise rotate a 32-bit number to the left.
790 | */
791 | var bit_rol = function (num, cnt) {
792 | return (num << cnt) | (num >>> (32 - cnt));
793 | };
794 |
795 | /*
796 | * Convert a string to an array of little-endian words
797 | * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
798 | */
799 | var str2binl = function (str) {
800 | var bin = [];
801 | var mask = (1 << chrsz) - 1;
802 | for(var i = 0; i < str.length * chrsz; i += chrsz)
803 | {
804 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
805 | }
806 | return bin;
807 | };
808 |
809 | /*
810 | * Convert an array of little-endian words to a string
811 | */
812 | var binl2str = function (bin) {
813 | var str = "";
814 | var mask = (1 << chrsz) - 1;
815 | for(var i = 0; i < bin.length * 32; i += chrsz)
816 | {
817 | str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
818 | }
819 | return str;
820 | };
821 |
822 | /*
823 | * Convert an array of little-endian words to a hex string.
824 | */
825 | var binl2hex = function (binarray) {
826 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
827 | var str = "";
828 | for(var i = 0; i < binarray.length * 4; i++)
829 | {
830 | str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
831 | hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
832 | }
833 | return str;
834 | };
835 |
836 | /*
837 | * Convert an array of little-endian words to a base-64 string
838 | */
839 | var binl2b64 = function (binarray) {
840 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
841 | var str = "";
842 | var triplet, j;
843 | for(var i = 0; i < binarray.length * 4; i += 3)
844 | {
845 | triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) |
846 | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) |
847 | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
848 | for(j = 0; j < 4; j++)
849 | {
850 | if(i * 8 + j * 6 > binarray.length * 32) { str += b64pad; }
851 | else { str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); }
852 | }
853 | }
854 | return str;
855 | };
856 |
857 | /*
858 | * These functions implement the four basic operations the algorithm uses.
859 | */
860 | var md5_cmn = function (q, a, b, x, s, t) {
861 | return safe_add(bit_rol(safe_add(safe_add(a, q),safe_add(x, t)), s),b);
862 | };
863 |
864 | var md5_ff = function (a, b, c, d, x, s, t) {
865 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
866 | };
867 |
868 | var md5_gg = function (a, b, c, d, x, s, t) {
869 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
870 | };
871 |
872 | var md5_hh = function (a, b, c, d, x, s, t) {
873 | return md5_cmn(b ^ c ^ d, a, b, x, s, t);
874 | };
875 |
876 | var md5_ii = function (a, b, c, d, x, s, t) {
877 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
878 | };
879 |
880 | /*
881 | * Calculate the MD5 of an array of little-endian words, and a bit length
882 | */
883 | var core_md5 = function (x, len) {
884 | /* append padding */
885 | x[len >> 5] |= 0x80 << ((len) % 32);
886 | x[(((len + 64) >>> 9) << 4) + 14] = len;
887 |
888 | var a = 1732584193;
889 | var b = -271733879;
890 | var c = -1732584194;
891 | var d = 271733878;
892 |
893 | var olda, oldb, oldc, oldd;
894 | for (var i = 0; i < x.length; i += 16)
895 | {
896 | olda = a;
897 | oldb = b;
898 | oldc = c;
899 | oldd = d;
900 |
901 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
902 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
903 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
904 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
905 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
906 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
907 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
908 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
909 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
910 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
911 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
912 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
913 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
914 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
915 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
916 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
917 |
918 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
919 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
920 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
921 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
922 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
923 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
924 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
925 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
926 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
927 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
928 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
929 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
930 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
931 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
932 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
933 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
934 |
935 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
936 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
937 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
938 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
939 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
940 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
941 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
942 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
943 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
944 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
945 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
946 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
947 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
948 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
949 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
950 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
951 |
952 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
953 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
954 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
955 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
956 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
957 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
958 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
959 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
960 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
961 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
962 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
963 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
964 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
965 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
966 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
967 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
968 |
969 | a = safe_add(a, olda);
970 | b = safe_add(b, oldb);
971 | c = safe_add(c, oldc);
972 | d = safe_add(d, oldd);
973 | }
974 | return [a, b, c, d];
975 | };
976 |
977 |
978 | /*
979 | * Calculate the HMAC-MD5, of a key and some data
980 | */
981 | var core_hmac_md5 = function (key, data) {
982 | var bkey = str2binl(key);
983 | if(bkey.length > 16) { bkey = core_md5(bkey, key.length * chrsz); }
984 |
985 | var ipad = new Array(16), opad = new Array(16);
986 | for(var i = 0; i < 16; i++)
987 | {
988 | ipad[i] = bkey[i] ^ 0x36363636;
989 | opad[i] = bkey[i] ^ 0x5C5C5C5C;
990 | }
991 |
992 | var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
993 | return core_md5(opad.concat(hash), 512 + 128);
994 | };
995 |
996 | var obj = {
997 | /*
998 | * These are the functions you'll usually want to call.
999 | * They take string arguments and return either hex or base-64 encoded
1000 | * strings.
1001 | */
1002 | hexdigest: function (s) {
1003 | return binl2hex(core_md5(str2binl(s), s.length * chrsz));
1004 | },
1005 |
1006 | b64digest: function (s) {
1007 | return binl2b64(core_md5(str2binl(s), s.length * chrsz));
1008 | },
1009 |
1010 | hash: function (s) {
1011 | return binl2str(core_md5(str2binl(s), s.length * chrsz));
1012 | },
1013 |
1014 | hmac_hexdigest: function (key, data) {
1015 | return binl2hex(core_hmac_md5(key, data));
1016 | },
1017 |
1018 | hmac_b64digest: function (key, data) {
1019 | return binl2b64(core_hmac_md5(key, data));
1020 | },
1021 |
1022 | hmac_hash: function (key, data) {
1023 | return binl2str(core_hmac_md5(key, data));
1024 | },
1025 |
1026 | /*
1027 | * Perform a simple self-test to see if the VM is working
1028 | */
1029 | test: function () {
1030 | return MD5.hexdigest("abc") === "900150983cd24fb0d6963f7d28e17f72";
1031 | }
1032 | };
1033 |
1034 | return obj;
1035 | })();
1036 |
--------------------------------------------------------------------------------