Mercurial > eldonilo > lightstring
comparison XMPP.js @ 0:96898e3812a5
initial push
author | Sonny Piers <sonny.piers@gmail.com> |
---|---|
date | Sun, 18 Dec 2011 19:03:28 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:96898e3812a5 |
---|---|
1 'use strict'; | |
2 | |
3 var Lightstring = { | |
4 NS: {}, | |
5 stanza: {}, | |
6 }; | |
7 | |
8 Lightstring.Connection = function (aURL) { | |
9 var parser = new DOMParser(); | |
10 var serializer = new XMLSerializer(); | |
11 this.handlers = {}; | |
12 this.iqid = 1024; | |
13 this.getNewId = function() { | |
14 this.iqid++; | |
15 return 'sendiq:'+this.iqid; | |
16 }; | |
17 this.parse = function(str) { | |
18 return parser.parseFromString(str, 'text/xml').documentElement; | |
19 }; | |
20 this.serialize = function(elm) { | |
21 return serializer.serializeToString(elm); | |
22 }; | |
23 this.connect = function(jid, password) { | |
24 this.domain = jid.split('@')[1]; | |
25 this.node = jid.split('@')[0]; | |
26 this.jid = jid; | |
27 this.password = password; | |
28 if(typeof WebSocket === 'undefined') | |
29 this.socket = new MozWebSocket(aURL); | |
30 else | |
31 this.socket = new WebSocket(aURL); | |
32 | |
33 var that = this; | |
34 this.socket.addEventListener('open', function() { | |
35 that.emit('open'); | |
36 that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); | |
37 }); | |
38 this.socket.addEventListener('error', function(err) { | |
39 that.emit('error'); | |
40 }); | |
41 this.socket.addEventListener('close', function(close) { | |
42 that.emit('close'); | |
43 that.emit('disconnected'); | |
44 }); | |
45 this.socket.addEventListener('message', function(e) { | |
46 that.emit('XMLInput', e.data); | |
47 var elm = that.parse(e.data); | |
48 that.emit('DOMInput', elm); | |
49 that.emit(elm.tagName, elm); | |
50 | |
51 if((elm.tagName === 'iq')) | |
52 that.emit(elm.getAttribute('id'), elm); | |
53 }); | |
54 }; | |
55 | |
56 | |
57 | |
58 this.send = function(stanza, callback) { | |
59 //FIXME support for E4X | |
60 //~ if(typeof stanza === 'xml') { | |
61 //~ stanza = stanza.toXMLString(); | |
62 //~ } | |
63 if(stanza.cnode) { | |
64 stanza = stanza.toString(); | |
65 //~ console.log(typeof stanza); | |
66 } | |
67 if(typeof stanza === 'string') { | |
68 var str = stanza; | |
69 var elm = this.parse(stanza); | |
70 } | |
71 else { | |
72 var elm = stanza; | |
73 var str = this.serialize(stanza); | |
74 } | |
75 | |
76 | |
77 if(elm.tagName === 'iq') { | |
78 var id = elm.getAttribute('id'); | |
79 if(!id) { | |
80 elm.setAttribute('id', this.getNewId()) | |
81 str = this.serialize(elm) | |
82 } | |
83 if(callback) this.on(elm.getAttribute('id'), callback); | |
84 } | |
85 | |
86 | |
87 this.socket.send(str); | |
88 this.emit('XMLOutput', str); | |
89 this.emit('DOMOutput', elm); | |
90 }; | |
91 this.disconnect = function() { | |
92 this.send('</stream:stream>'); | |
93 this.emit('disconnected'); | |
94 this.socket.close(); | |
95 }; | |
96 //FIXME Callbacks sucks, better idea? | |
97 this.emit = function(name, data) { | |
98 var handlers = this.handlers[name]; | |
99 if(!handlers) | |
100 return; | |
101 | |
102 //FIXME Better idea than passing the context as argument? | |
103 for(var i=0; i<handlers.length; i++) | |
104 handlers[i](data, this); | |
105 | |
106 if(name.match('sendiq:')) | |
107 delete this.handlers[name]; | |
108 }; | |
109 this.on = function(name, callback) { | |
110 if(!this.handlers[name]) | |
111 this.handlers[name] = []; | |
112 this.handlers[name].push(callback); | |
113 }; | |
114 //FIXME do this! | |
115 this.once = function(name, callback) { | |
116 if(!this.handlers[name]) | |
117 this.handlers[name] = []; | |
118 this.handlers[name].push(callback); | |
119 }; | |
120 //Internal | |
121 this.on('stream:features', function(stanza, that) { | |
122 var nodes = stanza.querySelectorAll('mechanism'); | |
123 //SASL/Auth features | |
124 if(nodes.length > 0) { | |
125 that.emit('mechanisms', stanza); | |
126 var mechanisms = {}; | |
127 for(var i=0; i<nodes.length; i++) | |
128 mechanisms[nodes[i].textContent] = true; | |
129 | |
130 | |
131 //FIXME support SCRAM-SHA1 && allow specify method preferences | |
132 if('DIGEST-MD5' in mechanisms) | |
133 that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"); | |
134 else if('PLAIN' in mechanisms) { | |
135 var token = btoa(that.jid + "\u0000" + that.jid.split('@')[0] + "\u0000" + that.password); | |
136 that.send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"+token+"</auth>"); | |
137 } | |
138 } | |
139 //XMPP features | |
140 else { | |
141 that.emit('features', stanza); | |
142 //Bind http://xmpp.org/rfcs/rfc3920.html#bind | |
143 that.send("<iq type='set' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>", function() { | |
144 //Session http://xmpp.org/rfcs/rfc3921.html#session | |
145 that.send("<iq type='set' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>", function() { | |
146 that.emit('connected'); | |
147 }); | |
148 }); | |
149 } | |
150 }); | |
151 //Internal | |
152 this.on('success', function(stanza, that) { | |
153 that.send("<stream:stream to='"+that.domain+"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' />"); | |
154 }); | |
155 //Internal | |
156 this.on('challenge', function(stanza, that) { | |
157 //FIXME this is mostly Strophe code | |
158 | |
159 function _quote(str) { | |
160 return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; | |
161 }; | |
162 | |
163 var challenge = atob(stanza.textContent); | |
164 | |
165 var attribMatch = /([a-z]+)=("[^"]+"|[^,"]+)(?:,|$)/; | |
166 | |
167 var cnonce = MD5.hexdigest(Math.random() * 1234567890); | |
168 var realm = ""; | |
169 var host = null; | |
170 var nonce = ""; | |
171 var qop = ""; | |
172 var matches; | |
173 | |
174 while (challenge.match(attribMatch)) { | |
175 matches = challenge.match(attribMatch); | |
176 challenge = challenge.replace(matches[0], ""); | |
177 matches[2] = matches[2].replace(/^"(.+)"$/, "$1"); | |
178 switch (matches[1]) { | |
179 case "realm": | |
180 realm = matches[2]; | |
181 break; | |
182 case "nonce": | |
183 nonce = matches[2]; | |
184 break; | |
185 case "qop": | |
186 qop = matches[2]; | |
187 break; | |
188 case "host": | |
189 host = matches[2]; | |
190 break; | |
191 } | |
192 } | |
193 | |
194 var digest_uri = "xmpp/" + that.domain; | |
195 if (host !== null) { | |
196 digest_uri = digest_uri + "/" + host; | |
197 } | |
198 | |
199 var A1 = MD5.hash(that.node + | |
200 ":" + realm + ":" + that.password) + | |
201 ":" + nonce + ":" + cnonce; | |
202 var A2 = 'AUTHENTICATE:' + digest_uri; | |
203 | |
204 var responseText = ""; | |
205 responseText += 'username=' + _quote(that.node) + ','; | |
206 responseText += 'realm=' + _quote(realm) + ','; | |
207 responseText += 'nonce=' + _quote(nonce) + ','; | |
208 responseText += 'cnonce=' + _quote(cnonce) + ','; | |
209 responseText += 'nc="00000001",'; | |
210 responseText += 'qop="auth",'; | |
211 responseText += 'digest-uri=' + _quote(digest_uri) + ','; | |
212 responseText += 'response=' + _quote( | |
213 MD5.hexdigest(MD5.hexdigest(A1) + ":" + | |
214 nonce + ":00000001:" + | |
215 cnonce + ":auth:" + | |
216 MD5.hexdigest(A2))) + ','; | |
217 responseText += 'charset="utf-8"'; | |
218 | |
219 that.send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"+btoa(responseText)+"</response>"); | |
220 }); | |
221 }; |