LP#1666706: add --with-websockets-port configure option
[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 // TRANSPORT_SOCKET
164
165 /** Initializes a new jabber_socket object */
166 function jabber_socket() {
167
168     this.is_connected    = false;
169     this.outstream        = "";
170     this.instream        = "";
171     this.buffer            = "";
172     this.socket            = "";
173
174 }
175
176 /** Connects to the jabber server */
177 jabber_socket.prototype.connect = 
178     function( username, password, resource,  host, port, timeout ) {
179
180     var starttime = new Date().getTime();
181
182     // there has to be at least some kind of timeout
183     if( ! timeout || timeout < 100 ) { timeout = 1000; }
184
185     try {
186
187         this.xpcom_init( host, port );
188         this.tcp_send( "<stream:stream to='"+host
189                 +"' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>" );
190
191         if( !this.tcp_recv( timeout ) ) {  throw 1; }
192
193     } catch( E ) {
194         throw new oils_ex_transport( "Could not open a socket to the transport server\n" 
195                 + "Server: " + host + " Port: " + port  );
196     }
197
198     // Send the auth packet
199     this.tcp_send( "<iq id='123456789' type='set'><query xmlns='jabber:iq:auth'><username>" 
200             + username + "</username><password>" + password + 
201             "</password><resource>" + resource + "</resource></query></iq>" );
202
203     var cur = new Date().getTime();
204     var remaining = timeout - ( cur - starttime );
205     this.tcp_recv( remaining );
206
207     if( ! this.connected() ) {
208         throw new oils_ex_transport( "Connection to transport server timed out" );
209     }
210
211     return true;
212 };
213
214
215 /** Sets up all of the xpcom components */
216 jabber_socket.prototype.xpcom_init = function( host, port ) {
217
218     var transportService =
219         Components.classes["@mozilla.org/network/socket-transport-service;1"]
220         .getService(Components.interfaces.nsISocketTransportService);
221
222     this.transport = transportService.createTransport( null, 0, host, port, null);
223
224     // ------------------------------------------------------------------
225     // Build the stream objects
226     // ------------------------------------------------------------------
227     this.outstream = this.transport.openOutputStream(0,0,0);
228     
229     var stream = this.transport.openInputStream(0,0,0);
230
231     this.instream = Components.classes["@mozilla.org/scriptableinputstream;1"]
232             .createInstance(Components.interfaces.nsIScriptableInputStream);
233
234     this.instream.init(stream);
235
236 };
237
238 /** Send data to the TCP pipe */
239 jabber_socket.prototype.tcp_send = function( data ) {
240     new Logger().transport( "Sending Data: \n" + data, Logger.INFO );
241     this.outstream.write(data,data.length);
242 };
243
244
245 /** Accepts data coming directly from the socket.  If we're not
246   * connected, we pass it off to procecc_connect().  Otherwise,
247   * this method adds the data to the local buffer.
248   */
249 jabber_socket.prototype.process_data = function( data ) {
250
251     new Logger().transport( "Received TCP data: " + data, Logger.DEBUG );
252
253     if( ! this.connected() ) {
254         this.process_connect( data );
255         return;
256     } 
257
258     this.buffer += data;
259
260 };
261
262 /** Processes connect data to verify we are logged in correctly */
263 jabber_socket.prototype.process_connect = function( data ) {
264
265     var reg = /type=["\']result["\']/;
266     var err = /error/;
267
268     if( reg.exec( data ) ) {
269         this.is_connected = true;
270     } else {
271         if( err.exec( data ) ) {
272             //throw new oils_ex_transport( "Server returned: \n" + data );
273             throw new oils_ex_jabber_auth( "Server returned: \n" + data );
274             // Throw exception, return something...
275         }
276     }
277 };
278
279 /** Waits up to at most 'timeout' milliseconds for data to arrive 
280   * in the TCP buffer.  If there is at least one byte of data 
281   * in the buffer, then all of the data that is in the buffer is sent 
282   * to the process_data method for processing and the method returns.  
283   */
284 jabber_socket.prototype.tcp_recv = function( timeout ) {
285
286     var count = this.instream.available();
287     var did_receive = false;
288
289     // ------------------------------------------------------------------
290     // If there is any data in the tcp buffer, process it and return
291     // ------------------------------------------------------------------
292     if( count > 0 ) { 
293
294         did_receive = true;
295         while( count > 0 ) { 
296             new Logger().transport(
297                 "before process data", Logger.DEBUG );
298
299             this.process_data( this.instream.read( count ) );
300
301             new Logger().transport(
302                 "after process data", Logger.DEBUG );
303
304             count = this.instream.available();
305
306             new Logger().transport(
307                 "received " + count + " bytes" , Logger.DEBUG );
308         }
309
310     } else { 
311
312         // ------------------------------------------------------------------
313         // Do the timeout dance
314         // ------------------------------------------------------------------
315
316         // ------------------------------------------------------------------
317         // If there is no data in the buffer, wait up to timeout seconds
318         // for some data to arrive.  Once it arrives, suck it all out
319         // and send it on for processing
320         // ------------------------------------------------------------------
321
322         var now, then;
323         now = new Date().getTime();
324         then = now;
325
326         // ------------------------------------------------------------------
327         // Loop and poll for data every 50 ms.
328         // ------------------------------------------------------------------
329         while( ((now-then) <= timeout) && count <= 0 ) { 
330             sleep(50);
331             count = this.instream.available();
332             now = new Date().getTime();
333         }
334
335         // ------------------------------------------------------------------
336         // if we finally get some data, process it.
337         // ------------------------------------------------------------------
338         if( count > 0 ) {
339
340             did_receive = true;
341             while( count > 0 ) { // pull in all of the data there is
342                 this.process_data( this.instream.read( count ) );
343                 count = this.instream.available();
344             }
345         }
346     }
347
348     return did_receive;
349
350 };
351
352 /** If a message is already sitting in the queue, it is returned.  
353   * If not, this method waits till at most 'timeout' milliseconds 
354   * for a full jabber message to arrive and then returns that.
355   * If none ever arrives, returns null.
356   */
357 jabber_socket.prototype.recv = function( timeout ) {
358
359     var now, then;
360     now = new Date().getTime();
361     then = now;
362
363     var first_pass = true;
364     while( ((now-then) <= timeout) ) {
365         
366         if( this.buffer.length == 0  || !first_pass ) {
367             if( ! this.tcp_recv( timeout ) ) {
368                 return null;
369             }
370         }
371         first_pass = false;
372
373         //new Logger().transport( "\n\nTCP Buffer Before: \n" + this.buffer, Logger.DEBUG );
374
375         var buf = this.buffer;
376         this.buffer = "";
377
378         new Logger().transport( "CURRENT BUFFER\n" + buf,
379             Logger.DEBUG );
380
381         buf = buf.replace( /\n/g, '' ); // remove pesky newlines
382
383         var reg = /<message.*?>.*?<\/message>/;
384         var iqr = /<iq.*?>.*?<\/iq>/;
385         var out = reg.exec(buf);
386
387         if( out ) { 
388
389             var msg_xml = out[0];
390             this.buffer = buf.substring( msg_xml.length, buf.length );
391             new Logger().transport( "Building Jabber message\n\n" + msg_xml, Logger.DEBUG );
392             var jab_msg = new jabber_message().from_xml( msg_xml );
393             if( jab_msg.is_error_msg ) {
394                 new Logger().transport( "Received Jabber error message \n\n" + msg_xml, Logger.ERROR );
395             } 
396
397             return jab_msg;
398
399
400         } else { 
401
402             out = iqr.exec(buf);
403
404             if( out ) {
405                 var msg_xml = out[0];
406                 this.buffer = buf.substring( msg_xml.length, buf.length );
407                 process_iq_data( msg_xml );
408                 return;
409
410             } else {
411                 this.buffer = buf;
412             }
413
414         } 
415         now = new Date().getTime();
416     }
417
418     return null;
419 };
420
421 jabber_socket.prototype.process_iq_data = function( data ) {
422     new Logger().transport( "IQ Packet received... Not Implemented\n" + data, Logger.ERROR );
423 };
424
425 /** Disconnects from the jabber server and closes down shop */
426 jabber_socket.prototype.disconnect = function() {
427     this.tcp_send( "</stream:stream>" );
428     this.instream.close();
429     this.outstream.close();
430 };
431
432 /** True if connected */
433 jabber_socket.prototype.connected = function() {
434     return this.is_connected;
435 };
436
437
438
439
440