]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/javascript/opensrf_xmpp.js
Performance tweak to the logging routines.
[OpenSRF.git] / src / javascript / opensrf_xmpp.js
1
2 /**
3  * XXX
4  * XXX For reference only until this code is updated to match new opensrf.js layout XXX
5  * XXX
6  */
7
8
9
10 // ------------------------------------------------------------------
11 //              Houses the jabber transport code
12 //
13 // 1. jabber_connection - high level jabber component
14 // 2. jabber_message - message class
15 // 3. jabber_socket - socket handling code (shouldn't have to 
16 //              use this class directly)
17 //
18 // Requires oils_utils.js
19 // ------------------------------------------------------------------
20
21
22
23
24
25 // ------------------------------------------------------------------
26 // JABBER_CONNECTION
27 // High level transport code
28
29 // ------------------------------------------------------------------
30 // Constructor
31 // ------------------------------------------------------------------
32 jabber_connection.prototype = new transport_connection();
33 jabber_connection.prototype.constructor = jabber_connection;
34 jabber_connection.baseClass = transport_connection.prototype.constructor;
35
36 /** Initializes a jabber_connection object */
37 function jabber_connection( username, password, resource ) {
38
39         this.username           = username;
40         this.password           = password;
41         this.resource           = resource;
42         this.socket                     = new jabber_socket();
43
44         this.host                       = "";
45
46 }
47
48 /** Connects to the Jabber server.  'timeout' is the connect timeout
49   * in milliseconds 
50  */
51 jabber_connection.prototype.connect = function( host, port, timeout ) {
52         this.host = host;
53         return this.socket.connect( 
54                         this.username, this.password, this.resource, host, port, timeout );
55 }
56
57 /** Sends a message to 'recipient' with the provided message 
58   * thread and body 
59   */
60 jabber_connection.prototype.send = function( recipient, thread, body ) {
61         var jid = this.username+"@"+this.host+"/"+this.resource;
62         var msg = new jabber_message( jid, recipient, thread, body );
63         return this.socket.tcp_send( msg.to_string() );
64 }
65
66 /** This method will wait at most 'timeout' milliseconds
67   * for a Jabber message to arrive.  If one arrives
68   * it is returned to the caller, other it returns null
69   */
70 jabber_connection.prototype.recv = function( timeout ) {
71         return this.socket.recv( timeout );
72 }
73
74 /** Disconnects from the jabber server */
75 jabber_connection.prototype.disconnect = function() {
76         return this.socket.disconnect();
77 }
78
79 /** Returns true if we are currently connected to the 
80   * Jabber server
81   */
82 jabber_connection.prototype.connected = function() {
83         return this.socket.connected();
84 }
85
86
87
88 // ------------------------------------------------------------------
89 // JABBER_MESSAGE
90 // High level message handling code
91         
92
93 jabber_message.prototype = new transport_message();
94 jabber_message.prototype.constructor = jabber_message;
95 jabber_message.prototype.baseClass = transport_message.prototype.constructor;
96
97 /** Builds a jabber_message object */
98 function jabber_message( sender, recipient, thread, body ) {
99
100         if( sender == null || recipient == null || recipient.length < 1 ) { return; }
101
102         this.doc = new DOMParser().parseFromString("<message></message>", "text/xml");
103         this.root = this.doc.documentElement;
104         this.root.setAttribute( "from", sender );
105         this.root.setAttribute( "to", recipient );
106
107         var body_node = this.doc.createElement("body");
108         body_node.appendChild( this.doc.createTextNode( body ) );
109
110         var thread_node = this.doc.createElement("thread");
111         thread_node.appendChild( this.doc.createTextNode( thread ) );
112
113         this.root.appendChild( body_node );
114         this.root.appendChild( thread_node );
115
116 }
117
118 /** Builds a new message from raw xml.
119   * If the message is a Jabber error message, then msg.is_error_msg
120   * is set to true;
121   */
122 jabber_message.prototype.from_xml = function( xml ) {
123         var msg = new jabber_message();
124         msg.doc = new DOMParser().parseFromString( xml, "text/xml" );
125         msg.root = msg.doc.documentElement;
126
127         if( msg.root.getAttribute( "type" ) == "error" ) {
128                 msg.is_error_msg = true;
129         } else {
130                 this.is_error_msg = false;
131         }
132
133         return msg;
134 }
135
136 /** Returns the 'from' field of the message */
137 jabber_message.prototype.get_sender = function() {
138         return this.root.getAttribute( "from" );
139 }
140
141 /** Returns the jabber thread */
142 jabber_message.prototype.get_thread = function() {
143         var nodes = this.root.getElementsByTagName( "thread" );
144         var thread_node = nodes.item(0);
145         return thread_node.firstChild.nodeValue;
146 }
147
148 /** Returns the message body */
149 jabber_message.prototype.get_body = function() {
150         var nodes = this.root.getElementsByTagName( "body" );
151         var body_node = nodes.item(0);
152         new Logger().transport( "Get Body returning:\n" + body_node.textContent, Logger.DEBUG );
153         return body_node.textContent;
154 }
155         
156 /** Returns the message as a whole as an XML string */
157 jabber_message.prototype.to_string = function() {
158    return new XMLSerializer().serializeToString(this.root);
159 }
160
161
162
163
164 // ------------------------------------------------------------------
165 // TRANSPORT_SOCKET
166
167 /** Initializes a new jabber_socket object */
168 function jabber_socket() {
169
170         this.is_connected       = false;
171         this.outstream          = "";
172         this.instream           = "";
173         this.buffer                     = "";
174         this.socket                     = "";
175
176 }
177
178 /** Connects to the jabber server */
179 jabber_socket.prototype.connect = 
180         function( username, password, resource,  host, port, timeout ) {
181
182         var starttime = new Date().getTime();
183
184         // there has to be at least some kind of timeout
185         if( ! timeout || timeout < 100 ) { timeout = 1000; }
186
187         try {
188
189                 this.xpcom_init( host, port );
190                 this.tcp_send( "<stream:stream to='"+host
191                                 +"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" );
192
193                 if( !this.tcp_recv( timeout ) ) {  throw 1; }
194
195         } catch( E ) {
196                 throw new oils_ex_transport( "Could not open a socket to the transport server\n" 
197                                 + "Server: " + host + " Port: " + port  );
198         }
199
200         // Send the auth packet
201         this.tcp_send( "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>" 
202                         + username + "</username><password>" + password + 
203                         "</password><resource>" + resource + "</resource></query></iq>" );
204
205         var cur = new Date().getTime();
206         var remaining = timeout - ( cur - starttime );
207         this.tcp_recv( remaining );
208
209         if( ! this.connected() ) {
210                 throw new oils_ex_transport( "Connection to transport server timed out" );
211         }
212
213         return true;
214
215
216 }
217
218
219 /** Sets up all of the xpcom components */
220 jabber_socket.prototype.xpcom_init = function( host, port ) {
221
222         var transportService =
223                 Components.classes["@mozilla.org/network/socket-transport-service;1"]
224                 .getService(Components.interfaces.nsISocketTransportService);
225
226         this.transport = transportService.createTransport( null, 0, host, port, null);
227
228         // ------------------------------------------------------------------
229         // Build the stream objects
230         // ------------------------------------------------------------------
231         this.outstream = this.transport.openOutputStream(0,0,0);
232         
233         var stream = this.transport.openInputStream(0,0,0);
234
235         this.instream = Components.classes["@mozilla.org/scriptableinputstream;1"]
236                         .createInstance(Components.interfaces.nsIScriptableInputStream);
237
238         this.instream.init(stream);
239
240 }
241
242 /** Send data to the TCP pipe */
243 jabber_socket.prototype.tcp_send = function( data ) {
244         new Logger().transport( "Sending Data: \n" + data, Logger.INFO );
245         this.outstream.write(data,data.length);
246 }
247
248
249 /** Accepts data coming directly from the socket.  If we're not
250   * connected, we pass it off to procecc_connect().  Otherwise,
251   * this method adds the data to the local buffer.
252   */
253 jabber_socket.prototype.process_data = function( data ) {
254
255         new Logger().transport( "Received TCP data: " + data, Logger.DEBUG );
256
257         if( ! this.connected() ) {
258                 this.process_connect( data );
259                 return;
260         } 
261
262         this.buffer += data;
263
264 }
265
266 /** Processes connect data to verify we are logged in correctly */
267 jabber_socket.prototype.process_connect = function( data ) {
268
269         var reg = /type=["\']result["\']/;
270         var err = /error/;
271
272         if( reg.exec( data ) ) {
273                 this.is_connected = true;
274         } else {
275                 if( err.exec( data ) ) {
276                         //throw new oils_ex_transport( "Server returned: \n" + data );
277                         throw new oils_ex_jabber_auth( "Server returned: \n" + data );
278                         // Throw exception, return something...
279                 }
280         }
281 }
282
283 /** Waits up to at most 'timeout' milliseconds for data to arrive 
284   * in the TCP buffer.  If there is at least one byte of data 
285   * in the buffer, then all of the data that is in the buffer is sent 
286   * to the process_data method for processing and the method returns.  
287   */
288 jabber_socket.prototype.tcp_recv = function( timeout ) {
289
290         var count = this.instream.available();
291         var did_receive = false;
292
293         // ------------------------------------------------------------------
294         // If there is any data in the tcp buffer, process it and return
295         // ------------------------------------------------------------------
296         if( count > 0 ) { 
297
298                 did_receive = true;
299                 while( count > 0 ) { 
300                         new Logger().transport(
301                                 "before process data", Logger.DEBUG );
302
303                         this.process_data( this.instream.read( count ) );
304
305                         new Logger().transport(
306                                 "after process data", Logger.DEBUG );
307
308                         count = this.instream.available();
309
310                         new Logger().transport(
311                                 "received " + count + " bytes" , Logger.DEBUG );
312                 }
313
314         } else { 
315
316                 // ------------------------------------------------------------------
317                 // Do the timeout dance
318                 // ------------------------------------------------------------------
319
320                 // ------------------------------------------------------------------
321                 // If there is no data in the buffer, wait up to timeout seconds
322                 // for some data to arrive.  Once it arrives, suck it all out
323                 // and send it on for processing
324                 // ------------------------------------------------------------------
325
326                 var now, then;
327                 now = new Date().getTime();
328                 then = now;
329
330                 // ------------------------------------------------------------------
331                 // Loop and poll for data every 50 ms.
332                 // ------------------------------------------------------------------
333                 while( ((now-then) <= timeout) && count <= 0 ) { 
334                         sleep(50);
335                         count = this.instream.available();
336                         now = new Date().getTime();
337                 }
338
339                 // ------------------------------------------------------------------
340                 // if we finally get some data, process it.
341                 // ------------------------------------------------------------------
342                 if( count > 0 ) {
343
344                         did_receive = true;
345                         while( count > 0 ) { // pull in all of the data there is
346                                 this.process_data( this.instream.read( count ) );
347                                 count = this.instream.available();
348                         }
349                 }
350         }
351
352         return did_receive;
353
354 }
355
356 /** If a message is already sitting in the queue, it is returned.  
357   * If not, this method waits till at most 'timeout' milliseconds 
358   * for a full jabber message to arrive and then returns that.
359   * If none ever arrives, returns null.
360   */
361 jabber_socket.prototype.recv = function( timeout ) {
362
363         var now, then;
364         now = new Date().getTime();
365         then = now;
366
367         var first_pass = true;
368         while( ((now-then) <= timeout) ) {
369                 
370                 if( this.buffer.length == 0  || !first_pass ) {
371                         if( ! this.tcp_recv( timeout ) ) {
372                                 return null;
373                         }
374                 }
375                 first_pass = false;
376
377                 //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG );
378
379                 var buf = this.buffer;
380                 this.buffer = "";
381
382                 new Logger().transport( "CURRENT BUFFER\n" + buf,
383                         Logger.DEBUG );
384
385                 buf = buf.replace( /\n/g, '' ); // remove pesky newlines
386
387                 var reg = /<message.*?>.*?<\/message>/;
388                 var iqr = /<iq.*?>.*?<\/iq>/;
389                 var out = reg.exec(buf);
390
391                 if( out ) { 
392
393                         var msg_xml = out[0];
394                         this.buffer = buf.substring( msg_xml.length, buf.length );
395                         new Logger().transport( "Building Jabber message\n\n" + msg_xml, Logger.DEBUG );
396                         var jab_msg = new jabber_message().from_xml( msg_xml );
397                         if( jab_msg.is_error_msg ) {
398                                 new Logger().transport( "Received Jabber error message \n\n" + msg_xml, Logger.ERROR );
399                         } 
400
401                         return jab_msg;
402
403
404                 } else { 
405
406                         out = iqr.exec(buf);
407
408                         if( out ) {
409                                 var msg_xml = out[0];
410                                 this.buffer = buf.substring( msg_xml.length, buf.length );
411                                 process_iq_data( msg_xml );
412                                 return;
413
414                         } else {
415                                 this.buffer = buf;
416                         }
417
418                 } 
419                 now = new Date().getTime();
420         }
421
422         return null;
423 }
424
425 jabber_socket.prototype.process_iq_data = function( data ) {
426         new Logger().transport( "IQ Packet received... Not Implemented\n" + data, Logger.ERROR );
427 }
428
429 /** Disconnects from the jabber server and closes down shop */
430 jabber_socket.prototype.disconnect = function() {
431         this.tcp_send( "</stream:stream>" );
432         this.instream.close();
433         this.outstream.close();
434 }
435
436 /** True if connected */
437 jabber_socket.prototype.connected = function() {
438         return this.is_connected;
439 }
440
441
442
443
444