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 };