Mercurial > eldonilo > lightstring
comparison lightstring.js @ 99:f14558915187
bosh support
author | Sonny Piers <sonny.piers@gmail.com> |
---|---|
date | Tue, 12 Jun 2012 19:44:53 +0200 |
parents | 11e38a9bfe38 |
children | c06ec02217ee |
comparison
equal
deleted
inserted
replaced
98:6ec16b3e9cfc | 99:f14558915187 |
---|---|
116 })() | 116 })() |
117 }; | 117 }; |
118 | 118 |
119 /** | 119 /** |
120 * @constructor Creates a new Lightstring connection | 120 * @constructor Creates a new Lightstring connection |
121 * @param {String} [aService] The Websocket service URL. | 121 * @param {String} [aService] The connection manager URL. |
122 * @memberOf Lightstring | 122 * @memberOf Lightstring |
123 */ | 123 */ |
124 Lightstring.Connection = function(aService) { | 124 Lightstring.Connection = function(aService) { |
125 if (aService) | 125 if (aService) |
126 this.service = aService; | 126 this.service = aService; |
131 /** | 131 /** |
132 * @namespace Holds connection iq callbacks | 132 * @namespace Holds connection iq callbacks |
133 */ | 133 */ |
134 this.callbacks = {}; | 134 this.callbacks = {}; |
135 }; | 135 }; |
136 Lightstring.Connection.prototype = { | 136 Lightstring.Connection.prototype = new EventEmitter(); |
137 /** | 137 /** |
138 * @function Create and open a websocket then go though the XMPP authentification process. | 138 * @function Create and open a websocket then go though the XMPP authentification process. |
139 * @param {String} [aJid] The JID (Jabber id) to use. | 139 * @param {String} [aJid] The JID (Jabber id) to use. |
140 * @param {String} [aPassword] The associated password. | 140 * @param {String} [aPassword] The associated password. |
141 */ | 141 */ |
142 connect: function(aJid, aPassword) { | 142 Lightstring.Connection.prototype.connect = function(aJid, aPassword) { |
143 this.emit('connecting'); | 143 this.emit('connecting'); |
144 this.jid = new Lightstring.JID(aJid); | 144 this.jid = new Lightstring.JID(aJid); |
145 if (aPassword) | 145 if (aPassword) |
146 this.password = aPassword; | 146 this.password = aPassword; |
147 | 147 |
148 if (!this.jid.bare) | 148 if (!this.jid.bare) |
149 return; //TODO: error | 149 return; //TODO: error |
150 if (!this.service) | 150 if (!this.service) |
151 return; //TODO: error | 151 return; //TODO: error |
152 | 152 |
153 function getProtocol(aURL) { | 153 function getProtocol(aURL) { |
154 var a = document.createElement('a'); | 154 var a = document.createElement('a'); |
155 a.href = aURL; | 155 a.href = aURL; |
156 return a.protocol.replace(':', ''); | 156 return a.protocol.replace(':', ''); |
157 } | 157 } |
158 var protocol = getProtocol(this.service); | 158 var protocol = getProtocol(this.service); |
159 | 159 |
160 if (protocol.match('http')) | 160 if (protocol.match('http')) |
161 this.connection = new Lightstring.BOSHConnection(this.service); | 161 this.connection = new Lightstring.BOSHConnection(this.service); |
162 else if (protocol.match('ws')) | 162 else if (protocol.match('ws')) |
163 this.connection = new Lightstring.WebSocketConnection(this.service); | 163 this.connection = new Lightstring.WebSocketConnection(this.service); |
164 | 164 |
165 this.connection.connect(); | 165 this.connection.open(); |
166 | 166 |
167 var that = this; | 167 var that = this; |
168 | 168 |
169 this.connection.once('open', function() { | 169 this.connection.once('open', function() { |
170 var stream = Lightstring.stanzas.stream.open(that.jid.domain); | 170 that.emit('open'); |
171 that.connection.send(stream); | 171 }); |
172 var stanza = { | 172 this.connection.on('out', function(stanza) { |
173 XML: stream | 173 that.emit('out', stanza); |
174 }; | 174 }); |
175 that.emit('output', stanza); | 175 this.connection.on('in', function(stanza) { |
176 }); | 176 var stanza = new Lightstring.Stanza(stanza); |
177 this.connection.on('stanza', function(stanza) { | 177 |
178 var stanza = new Lightstring.Stanza(stanza); | 178 //FIXME: node-xmpp-bosh sends a self-closing stream:stream tag; it is wrong! |
179 | 179 that.emit('stanza', stanza); |
180 //FIXME: node-xmpp-bosh sends a self-closing stream:stream tag; it is wrong! | 180 |
181 that.emit('input', stanza); | 181 if (!stanza.DOM) |
182 | 182 return; |
183 if (!stanza.DOM) | 183 |
184 var name = stanza.DOM.localName; | |
185 | |
186 //Authentication | |
187 //FIXME SASL mechanisms and XMPP features can be both in a stream:features | |
188 if (name === 'features') { | |
189 //SASL mechanisms | |
190 if (stanza.DOM.firstChild.localName === 'mechanisms') { | |
191 stanza.mechanisms = []; | |
192 var nodes = stanza.DOM.getElementsByTagName('mechanism'); | |
193 for (var i = 0; i < nodes.length; i++) | |
194 stanza.mechanisms.push(nodes[i].textContent); | |
195 that.emit('mechanisms', stanza); | |
196 } | |
197 //XMPP features | |
198 else { | |
199 //TODO: stanza.features | |
200 that.emit('features', stanza); | |
201 } | |
202 } | |
203 else if (name === 'challenge') { | |
204 that.emit('challenge', stanza); | |
205 } | |
206 else if (name === 'failure') { | |
207 that.emit('failure', stanza); | |
208 } | |
209 else if (name === 'success') { | |
210 that.emit('success', stanza); | |
211 } | |
212 | |
213 //Iq callbacks | |
214 else if (name === 'iq') { | |
215 var payload = stanza.DOM.firstChild; | |
216 if (payload) | |
217 that.emit('iq/' + payload.namespaceURI + ':' + payload.localName, stanza); | |
218 | |
219 var id = stanza.DOM.getAttribute('id'); | |
220 if (!(id && id in that.callbacks)) | |
184 return; | 221 return; |
185 | 222 |
186 var name = stanza.DOM.localName; | |
187 | |
188 //Authentication | |
189 //FIXME SASL mechanisms and XMPP features can be both in a stream:features | |
190 if (name === 'features') { | |
191 //SASL mechanisms | |
192 if (stanza.DOM.firstChild.localName === 'mechanisms') { | |
193 stanza.mechanisms = []; | |
194 var nodes = stanza.DOM.getElementsByTagName('mechanism'); | |
195 for (var i = 0; i < nodes.length; i++) | |
196 stanza.mechanisms.push(nodes[i].textContent); | |
197 that.emit('mechanisms', stanza); | |
198 } | |
199 //XMPP features | |
200 else { | |
201 //TODO: stanza.features | |
202 that.emit('features', stanza); | |
203 } | |
204 } | |
205 else if (name === 'challenge') { | |
206 that.emit('challenge', stanza); | |
207 } | |
208 else if (name === 'failure') { | |
209 that.emit('failure', stanza); | |
210 } | |
211 else if (name === 'success') { | |
212 that.emit('success', stanza); | |
213 } | |
214 | |
215 //Iq callbacks | |
216 else if (name === 'iq') { | |
217 var payload = stanza.DOM.firstChild; | |
218 if (payload) | |
219 that.emit('iq/' + payload.namespaceURI + ':' + payload.localName, stanza); | |
220 | |
221 var id = stanza.DOM.getAttribute('id'); | |
222 if (!(id && id in that.callbacks)) | |
223 return; | |
224 | |
225 var type = stanza.DOM.getAttribute('type'); | |
226 if (type !== 'result' && type !== 'error') | |
227 return; //TODO: warning | |
228 | |
229 var callback = that.callbacks[id]; | |
230 if (type === 'result' && callback.success) | |
231 callback.success.call(that, stanza); | |
232 else if (type === 'error' && callback.error) | |
233 callback.error.call(that, stanza); | |
234 | |
235 delete that.callbacks[id]; | |
236 } | |
237 | |
238 else if (name === 'presence' || name === 'message') { | |
239 that.emit(name, stanza); | |
240 } | |
241 }); | |
242 }, | |
243 /** | |
244 * @function Send a message. | |
245 * @param {String|Object} aStanza The message to send. | |
246 * @param {Function} [aCallback] Executed on answer. (stanza must be iq) | |
247 */ | |
248 send: function(aStanza, aSuccess, aError) { | |
249 if (!(aStanza instanceof Lightstring.Stanza)) | |
250 var stanza = new Lightstring.Stanza(aStanza); | |
251 else | |
252 var stanza = aStanza; | |
253 | |
254 if (!stanza) | |
255 return; | |
256 | |
257 if (stanza.DOM.tagName === 'iq') { | |
258 var type = stanza.DOM.getAttribute('type'); | 223 var type = stanza.DOM.getAttribute('type'); |
259 if (type !== 'get' || type !== 'set') | 224 if (type !== 'result' && type !== 'error') |
260 ; //TODO: error | 225 return; //TODO: warning |
261 | 226 |
262 var callback = {success: aSuccess, error: aError}; | 227 var callback = that.callbacks[id]; |
263 | 228 if (type === 'result' && callback.success) |
264 var id = stanza.DOM.getAttribute('id'); | 229 callback.success.call(that, stanza); |
265 if (!id) { | 230 else if (type === 'error' && callback.error) |
266 var id = Lightstring.newId('sendiq:'); | 231 callback.error.call(that, stanza); |
267 stanza.DOM.setAttribute('id', id); | 232 |
268 } | 233 delete that.callbacks[id]; |
269 | 234 } |
270 this.callbacks[id] = callback; | 235 |
271 | 236 else if (name === 'presence' || name === 'message') { |
272 } | 237 that.emit(name, stanza); |
273 else if (aSuccess || aError) | 238 } |
274 ; //TODO: warning (no callback without iq) | 239 }); |
275 | 240 }; |
276 | 241 /** |
277 //FIXME this.socket.send(stanza.XML); (need some work on Lightstring.Stanza) | 242 * @function Send a message. |
278 var fixme = Lightstring.DOM2XML(stanza.DOM); | 243 * @param {String|Object} aStanza The message to send. |
279 stanza.XML = fixme; | 244 * @param {Function} [aCallback] Executed on answer. (stanza must be iq) |
280 this.connection.send(fixme); | 245 */ |
281 this.emit('output', stanza); | 246 Lightstring.Connection.prototype.send = function(aStanza, aSuccess, aError) { |
282 }, | 247 if (!(aStanza instanceof Lightstring.Stanza)) |
283 /** | 248 var stanza = new Lightstring.Stanza(aStanza); |
284 * @function Closes the XMPP stream and the socket. | 249 else |
285 */ | 250 var stanza = aStanza; |
286 disconnect: function() { | 251 |
287 this.emit('disconnecting'); | 252 if (!stanza) |
288 var stream = Lightstring.stanzas.stream.close(); | 253 return; |
289 this.socket.send(stream); | 254 |
290 this.emit('XMLOutput', stream); | 255 if (stanza.DOM.tagName === 'iq') { |
291 this.socket.close(); | 256 var type = stanza.DOM.getAttribute('type'); |
292 }, | 257 if (type !== 'get' || type !== 'set') |
293 load: function() { | 258 ; //TODO: error |
294 for (var i = 0; i < arguments.length; i++) { | 259 |
295 var name = arguments[i]; | 260 var callback = {success: aSuccess, error: aError}; |
296 if (!(name in Lightstring.plugins)) | 261 |
297 continue; //TODO: error | 262 var id = stanza.DOM.getAttribute('id'); |
298 | 263 if (!id) { |
299 var plugin = Lightstring.plugins[name]; | 264 var id = Lightstring.newId('sendiq:'); |
300 | 265 stanza.DOM.setAttribute('id', id); |
301 //Namespaces | 266 } |
302 for (var ns in plugin.namespaces) | 267 |
303 Lightstring.ns[ns] = plugin.namespaces[ns]; | 268 this.callbacks[id] = callback; |
304 | 269 |
305 //Stanzas | |
306 Lightstring.stanzas[name] = {}; | |
307 for (var stanza in plugin.stanzas) | |
308 Lightstring.stanzas[name][stanza] = plugin.stanzas[stanza]; | |
309 | |
310 //Handlers | |
311 for (var handler in plugin.handlers) | |
312 this.on(handler, plugin.handlers[handler]); | |
313 | |
314 //Methods | |
315 this[name] = {}; | |
316 for (var method in plugin.methods) | |
317 this[name][method] = plugin.methods[method].bind(this); | |
318 | |
319 if (plugin.init) | |
320 plugin.init.apply(this); | |
321 } | |
322 }, | |
323 /** | |
324 * @function Emits an event. | |
325 * @param {String} aName The event name. | |
326 * @param {Function|Array|Object} [aData] Data about the event. | |
327 */ | |
328 emit: function(aName, aData) { | |
329 var handlers = this.handlers[aName]; | |
330 if (!handlers) | |
331 return; | |
332 | |
333 //Non-data events | |
334 if(!aData) { | |
335 for (var i = 0; i < handlers.length; i++) | |
336 handlers[i].call(this, aData); | |
337 | |
338 return; | |
339 } | |
340 | |
341 //Non-iq events | |
342 if (aData && aData.DOM && aData.DOM.localName !== 'iq') { | |
343 for (var i = 0; i < handlers.length; i++) | |
344 handlers[i].call(this, aData); | |
345 | |
346 return; | |
347 } | |
348 | |
349 //Iq events | |
350 var ret; | |
351 for (var i = 0; i < handlers.length; i++) { | |
352 ret = handlers[i].call(this, aData); | |
353 if (typeof ret !== 'boolean') | |
354 return; //TODO: error | |
355 | |
356 if (ret) | |
357 return; | |
358 } | |
359 | |
360 if (aData && aData.DOM) { | |
361 var type = aData.DOM.getAttribute('type'); | |
362 if (type !== 'get' && type !== 'set') | |
363 return; | |
364 | |
365 var from = aData.DOM.getAttribute('from'); | |
366 var id = aData.DOM.getAttribute('id'); | |
367 this.send(Lightstring.stanzas.errors.iq(from, id, 'cancel', 'service-unavailable')); | |
368 } | |
369 }, | |
370 /** | |
371 * @function Register an event handler. | |
372 * @param {String} aName The event name. | |
373 * @param {Function} aCallback The callback to call when the event is emitted. | |
374 */ | |
375 on: function(aName, callback) { | |
376 if (!this.handlers[aName]) | |
377 this.handlers[aName] = []; | |
378 this.handlers[aName].push(callback); | |
379 } | 270 } |
380 }; | 271 else if (aSuccess || aError) |
272 ; //TODO: warning (no callback without iq) | |
273 | |
274 | |
275 //FIXME this.socket.send(stanza.XML); (need some work on Lightstring.Stanza) | |
276 var fixme = Lightstring.DOM2XML(stanza.DOM); | |
277 stanza.XML = fixme; | |
278 this.connection.send(fixme); | |
279 this.emit('output', stanza); | |
280 }; | |
281 /** | |
282 * @function Closes the XMPP stream and the socket. | |
283 */ | |
284 Lightstring.Connection.prototype.disconnect = function() { | |
285 this.emit('disconnecting'); | |
286 var stream = Lightstring.stanzas.stream.close(); | |
287 this.socket.send(stream); | |
288 this.emit('XMLOutput', stream); | |
289 this.socket.close(); | |
290 }; | |
291 Lightstring.Connection.prototype.load = function() { | |
292 for (var i = 0; i < arguments.length; i++) { | |
293 var name = arguments[i]; | |
294 if (!(name in Lightstring.plugins)) | |
295 continue; //TODO: error | |
296 | |
297 var plugin = Lightstring.plugins[name]; | |
298 | |
299 //Namespaces | |
300 for (var ns in plugin.namespaces) | |
301 Lightstring.ns[ns] = plugin.namespaces[ns]; | |
302 | |
303 //Stanzas | |
304 Lightstring.stanzas[name] = {}; | |
305 for (var stanza in plugin.stanzas) | |
306 Lightstring.stanzas[name][stanza] = plugin.stanzas[stanza]; | |
307 | |
308 //Handlers | |
309 for (var handler in plugin.handlers) | |
310 this.on(handler, plugin.handlers[handler]); | |
311 | |
312 //Methods | |
313 this[name] = {}; | |
314 for (var method in plugin.methods) | |
315 this[name][method] = plugin.methods[method].bind(this); | |
316 | |
317 if (plugin.init) | |
318 plugin.init.apply(this); | |
319 } | |
320 }; |