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