├── 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 | 85 | 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 | 17 | 18 |
19 |
20 |
21 |
22 | 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 | 60 | 61 | 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 += "
     
    " + options.to.split('@')[0] +"
    "; 94 | 95 | // chat box toolbaropenChat 96 | chat_window += "
    _ ×
    "; 97 | 98 | // Chat box actual conversation 99 | chat_window += "
    " 100 | 101 | // Chat box message box 102 | chat_window += "
    "; 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 =""; 412 | $.post(url,text,function(data){ 413 | //xmpp.messageHandler(data); 414 | xmpp.rid++; 415 | 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 | --------------------------------------------------------------------------------