1 /*
  2  ****************************************************************
  3  * Licensed Materials - Property of IBM
  4  * 5725-F96 IBM MessageSight
  5  * (C) Copyright IBM Corp.  2014.  All Rights Reserved.
  6  *
  7  * US Government Users Restricted Rights - Use, duplication or
  8  * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  9  ****************************************************************
 10  */
 11 MessagingREST = (function (global) {
 12 
 13 	var version = "1.0";
 14 
 15 	
 16 	/** 
 17 	 * The JavaScript application communicates with IBM MessageSight via http plug-in using 
 18 	 * a {@link MessagingREST.Client} object. 
 19 	 * 
 20 	 * The methods are implemented as asynchronous JavaScript methods (even though the 
 21 	 * underlying protocol exchange might be synchronous in nature). This means they signal 
 22 	 * their completion by calling back to the application via Success or Failure callback 
 23 	 * functions provided by the application on the method in question. These callbacks are 
 24 	 * called at most once per method invocation and do not persist beyond the lifetime of 
 25 	 * the script that made the invocation.
 26 	 * 
 27 	 * @name MessagingREST.Client    
 28 	 * 
 29 	 * @constructor
 30 	 * 
 31 	 * @param {string} host - the address of the IBM MessageSight host, as a DNS name or dotted decimal IP address.
 32 	 * @param {number} port - the port number to connect to
 33 	 * @param {string} path - the alias on the IBM MessageSight host.
 34 	 * @param {string} clientid - the MessagingREST client identifier
 35 	 * @param {string} username - the username to authenticate with
 36 	 * @param {string} password - the password for the username provided
 37 	 *   
 38 	 */
 39 	var Client = function (host, port, path, protocol, clientid, username, password) {
 40 		
 41 		var trace = function() {};
 42 		
 43 		
 44 		if (typeof host !== "string")
 45 			throw new Error("invalid server name");
 46 		
 47 		if (typeof port !== "number" || port < 0)
 48 			throw new Error("invalid port number");
 49 			
 50 		if (typeof path !== "string")
 51 			throw new Error("invalid path");
 52 		
 53 		if (!clientid) 
 54 			throw new Error("Client ID is required");
 55 		
 56 		if (typeof clientid !== "string")
 57 			throw new Error("invalid Client ID");
 58 
 59 		var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0,1) != "[" && host.slice(-1) != "]");
 60 		var url = protocol + "://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path;
 61 		
 62 		trace("Base URL is: " + url);
 63 		
 64 		
 65 		/**
 66 		 * Set a trace function that the client can use for
 67 		 * logging trace messages.
 68 		 * 
 69 		 * @param {function} trace - a trace function that takes one string as an argument
 70 		 */
 71 		this.setTrace = function(traceFunction) {
 72 			
 73 			if (traceFunction) {
 74 				trace = traceFunction;
 75 			}
 76 			
 77 		}
 78 
 79 		/** 
 80 		 * Set the necessary request headers for the a provided
 81 		 * XMLHttpRequest object. Since the request will more than
 82 		 * likely be a CORS request only headers that IBM MessageSight
 83 		 * allows may be set. 
 84 		 * @private
 85 		 * 
 86 		 * @name MessagingREST.Client#setRequestHeaders
 87 		 * @function
 88 		 * @param {object} request - The XMLHttpRequest object that headers should be set for.
 89 		 * 
 90 		 */
 91 		this.setRequestHeaders = function(request) {
 92 			
 93 			trace("Enter: Client.setRequestHeaders");
 94 					  
 95 			if (!request) {
 96 				return;
 97 			}
 98 			
 99 			if (username && password) {
100 		    	var userPass = btoa(username + ":" + password); 
101 		     	request.setRequestHeader("Authorization", "Basic " + userPass);
102 			}
103 			
104 			trace("setting header client-id to " + clientid);
105 			request.setRequestHeader("Client-ID", clientid);
106 			
107 		}
108 
109 		/** 
110 		 * Create a subscription for a given topic filter and a provided name.
111 		 * 
112 		 * 
113 		 * @name MessagingREST.Client#createTopicSubscription
114 		 * @function
115 		 * @param {string} filter - the topic filter that the subscription should be set to
116 		 * @param {string} subscriptionName - the name of the subscription
117 		 * @param {boolean} durable - if the subscription is durable or not
118 		 * @param {function} onSuccess - Called after a response with a status code of 200 is received
119 		 *                              from the server. A single response parameter is passed 
120 		 *                              to the onSuccess callback:
121 		 *                              <ol>     
122 		 *                              <li>a string that contains any response from the request   
123 		 *                              </ol>
124 		 * @param {function} onFailure - Called when a status code other than 200 is received from
125 		 *                               the server. Two response parameters are passed to the 
126 		 *                               onFailure callback:
127 		 *                              <ol> 
128 		 *								<li>a number that indicates the response code from the server    
129 		 *                              <li>a string that contains any response text from the server
130 		 *                              </ol>
131 		 * 
132 		 */
133 		this.createTopicSubscription = function(filter, subscriptionName, durable, onSuccess, onFailure) {
134 			
135 			trace("Enter: Client.createTopicSubscription");
136 			
137 		    // POST /restmsg/topic/filter?subscribe=name&durable=bool
138 			var filterEncoded = encodeURIComponent(filter);
139 			var requestURL = url + "/topic/" + filterEncoded + "?subscribe=" + subscriptionName + "&durable=" + durable.toString();
140 		    trace("Request URL is " + requestURL);
141 			
142 		    var xhrSubscribe = new XMLHttpRequest();
143 		    xhrSubscribe.open('POST', requestURL, true);
144 		    this.setRequestHeaders(xhrSubscribe);
145 		    
146 		    xhrSubscribe.onreadystatechange = function() {
147 		    	if (this.readyState != 4)  { return; }
148 		    	if (this.status === 200) {
149 		    		if (onSuccess) {
150 		    			onSuccess(this.responseText);
151 		    		}
152 		    	} else {
153 		    		if (onFailure) {
154 		    			onFailure(this.status, this.responseText);
155 		    		}
156 		    	}
157 		    	this.onreadystatechange = function() { };
158 		    };
159 		    
160 		    xhrSubscribe.send();
161 		    
162 		}
163 
164 
165 		/** 
166 		 * Obtain messages for current subscriptions. 
167 		 * 
168 		 * 
169 		 * @name MessagingREST.Client#retrieveMessages
170 		 * @function
171 		 * @param {number} timeout - The amount of time to wait for a message.
172 		 * @param {function} onSuccess - Called after a response with a status code of 200 is received
173 		 *                              from the server. A single response parameter is passed 
174 		 *                              to the onSuccess callback:
175 		 *                              <ol>     
176 		 *                              <li>a string that contains any response from the request   
177 		 *                              </ol>
178 		 * @param {function} onFailure - Called when a status code other than 200 is received from
179 		 *                               the server. Two response parameters are passed to the 
180 		 *                               onFailure callback:
181 		 *                              <ol> 
182 		 *								<li>a number that indicates the response code from the server    
183 		 *                              <li>a string that contains any response text from the server
184 		 *                              </ol>
185 		 * 
186 		 */
187 		this.retrieveMessages = function(timeout, onSuccess, onFailure) {
188 			
189 			trace("Enter: Client.retrieveMessages");
190 				
191 		    // GET /restmsg?timeout=int
192 			var requestUrl = url + "?timeout=" + timeout;
193 			trace("Request URL is " + requestUrl);
194 		    var xhrMsgs = new XMLHttpRequest();
195 		    
196 		    xhrMsgs.open('GET', requestUrl, true);
197 		    this.setRequestHeaders(xhrMsgs);
198 		    
199 		    xhrMsgs.onreadystatechange = function() {
200 		    	if (this.readyState != 4)  { return; }
201 		    	if (this.status === 200) {
202 		    		if (onSuccess) {
203 		    			var topic = this.getResponseHeader("Topic");
204 		    			var retained = this.getResponseHeader("Retained");
205 		    			onSuccess(topic, retained, this.responseText);
206 		    		}
207 		    	} else {
208 		    		if (onFailure) {
209 		    			onFailure(this.status, this.statusText);
210 		    		}
211 		    	}
212 		    	this.onreadystatechange = function() { };
213 		    };
214 		    
215 		    xhrMsgs.send();
216 		    
217 		}
218 		
219 		/** 
220 		 * Invoke action to obtain retained messages from topic. 
221 		 * This API will invoke an asynchronous call on the IBM 
222 		 * MessageSight server that will attempt to obtain a retained
223 		 * message. After successful callback from this API a check
224 		 * for the retained message can be done with a call to 
225 		 * MessagingREST.Client#retrieveMessages
226 		 * 
227 		 * 
228 		 * @name MessagingREST.Client#getRetainedMsg
229 		 * @function
230 		 * @param {string} topicName - The topic name to obtain a retained message from.
231 		 * @param {function} onSuccess - Called after a response with a status code of 200 is received
232 		 *                              from the server. A single response parameter is passed 
233 		 *                              to the onSuccess callback:
234 		 *                              <ol>     
235 		 *                              <li>a string that contains any response from the request   
236 		 *                              </ol>
237 		 * @param {function} onFailure - Called when a status code other than 200 is received from
238 		 *                               the server. Two response parameters are passed to the 
239 		 *                               onFailure callback:
240 		 *                              <ol> 
241 		 *								<li>a number that indicates the response code from the server    
242 		 *                              <li>a string that contains any response text from the server
243 		 *                              </ol>
244 		 * 
245 		 */
246 		this.getRetainedMsg = function(topicName, onSuccess, onFailure) {
247 			
248 			trace("Enter: Client.getRetainedMsg");
249 				
250 		    // GET /restmsg/topic/topicName
251 			var topicNameEncoded = encodeURIComponent(topicName);
252 			var requestUrl = url + "/topic/" + topicNameEncoded;
253 			trace("Request URL is " + requestUrl);
254 		    var xhrRetainedMsgs = new XMLHttpRequest();
255 		    
256 		    xhrRetainedMsgs.open('GET', requestUrl, true);
257 		    this.setRequestHeaders(xhrRetainedMsgs);
258 		    
259 		    xhrRetainedMsgs.onreadystatechange = function() {
260 		    	if (this.readyState != 4)  { return; }
261 		    	if (this.status === 200) {
262 		    		if (onSuccess) {
263 		    			onSuccess(this.responseText);
264 		    		}
265 		    	} else {
266 		    		if (onFailure) {
267 		    			onFailure(this.status, this.statusText);
268 		    		}
269 		    	}
270 		    	this.onreadystatechange = function() { };
271 		    };
272 		    
273 		    xhrRetainedMsgs.send();
274 		    
275 		}
276 		
277 		/** 
278 		 * Invoke action to delete retained message from a
279 		 * given topic. 
280 		 * 
281 		 * 
282 		 * @name MessagingREST.Client#deleteRetained
283 		 * @function
284 		 * @param {string} topicName - The topic that the retained message should be removed from.
285 		 * @param {function} onSuccess - Called after a response with a status code of 200 is received
286 		 *                              from the server. A single response parameter is passed 
287 		 *                              to the onSuccess callback:
288 		 *                              <ol>     
289 		 *                              <li>a string that contains any response from the request   
290 		 *                              </ol>
291 		 * @param {function} onFailure - Called when a status code other than 200 is received from
292 		 *                               the server. Two response parameters are passed to the 
293 		 *                               onFailure callback:
294 		 *                              <ol> 
295 		 *								<li>a number that indicates the response code from the server    
296 		 *                              <li>a string that contains any response text from the server
297 		 *                              </ol>
298 		 * 
299 		 */
300 		this.deleteRetained = function(topicName, onSuccess, onFailure) {
301 			
302 			trace("Enter: Client.deleteRetained");
303 				
304 		    // GET /restmsg/topic/topicName
305 			var topicNameEncoded = encodeURIComponent(topicName);
306 			var requestUrl = url + "/topic/" + topicNameEncoded;
307 			trace("Request URL is " + requestUrl);
308 		    var xhrDeleteRetained = new XMLHttpRequest();
309 		    
310 		    xhrDeleteRetained.open('PUT', requestUrl, true);
311 		    this.setRequestHeaders(xhrDeleteRetained);
312 		    
313 		    xhrDeleteRetained.onreadystatechange = function() {
314 		    	if (this.readyState != 4)  { return; }
315 		    	if (this.status === 200) {
316 		    		if (onSuccess) {
317 		    			onSuccess(this.responseText);
318 		    		}
319 		    	} else {
320 		    		if (onFailure) {
321 		    			onFailure(this.status, this.statusText);
322 		    		}
323 		    	}
324 		    	this.onreadystatechange = function() { };
325 		    };
326 		    
327 		    xhrDeleteRetained.send(null);
328 		    
329 		}
330 
331 		/** 
332 		 * Publish a message to a specific topic.
333 		 * 
334 		 * 
335 		 * @name MessagingREST.Client#createTopicSubscription
336 		 * @function
337 		 * @param {string} topicName - the topic filter that the subscription should be set to
338 		 * @param {boolean} persist - if the message should persist
339 		 * @param {boolean} retain - if the message should be retained
340 		 * @param {function} onSuccess - Called after a response with a status code of 200 is received
341 		 *                              from the server. A single response parameter is passed 
342 		 *                              to the onSuccess callback:
343 		 *                              <ol>     
344 		 *                              <li>a string that contains any response from the request   
345 		 *                              </ol>
346 		 * @param {function} onFailure - Called when a status code other than 200 is received from
347 		 *                               the server. Two response parameters are passed to the 
348 		 *                               onFailure callback:
349 		 *                              <ol> 
350 		 *								<li>a number that indicates the response code from the server    
351 		 *                              <li>a string that contains any response text from the server
352 		 *                              </ol>
353 		 * 
354 		 */
355 		this.publishMessageTopic = function(topicName, persist, retain, onSuccess, onFailure) {
356 			
357 			trace("Enter: Client.publishMessageTopic");
358 
359 		    // POST /restmsg/topic/topicname?persist=bool&retain=bool
360 		    var topicNameEncoded = encodeURIComponent(topicName);
361 			var requestUrl = url + "/topic/" + topicNameEncoded + "?persist=" + persist.toString() + "&retain=" + retain.toString();
362 			trace("Request URL is " + requestUrl);
363 			
364 		    var xhrPublish = new XMLHttpRequest();
365 		    xhrPublish.open('POST', requestUrl, true);
366 		    this.setRequestHeaders(xhrPublish);
367 		    xhrPublish.setRequestHeader("Content-Type", "text/plain");
368 
369 		    xhrPublish.onreadystatechange = function() {
370 		    	if (this.readyState != 4)  { return; }
371 		    	if (this.status === 200) {
372 		    		if (onSuccess) {
373 		    			onSuccess(this.responseText);
374 		    		}
375 		    	} else {
376 		    		if (onFailure) {
377 		    			onFailure(this.status, this.responseText);
378 		    		}
379 		    	}
380 		    	this.onreadystatechange = function() { };
381 		    };
382 		    
383 		    var msgObj = {message: topicForm.textMessage.value }
384 		    var msgString = JSON.stringify(msgObj);
385 		    xhrPublish.send(topicForm.textMessage.value);
386 		    
387 		}
388 
389 
390 		/** 
391 		 * Send an explicit disconnect request to IBM MessageSight
392 		 * 
393 		 * 
394 		 * @name MessagingREST.Client#sendDisconnect
395 		 * @function
396 		 * @param {number} timeout - Amount of time that IBM MessageSight should wait for the 
397 		 *                           request to complete.
398 		 * @param {function} onSuccess - Called after a response with a status code of 200 is received
399 		 *                              from the server. A single response parameter is passed 
400 		 *                              to the onSuccess callback:
401 		 *                              <ol>     
402 		 *                              <li>a string that contains any response from the request   
403 		 *                              </ol>
404 		 * @param {function} onFailure - Called when a status code other than 200 is received from
405 		 *                               the server. Two response parameters are passed to the 
406 		 *                               onFailure callback:
407 		 *                              <ol> 
408 		 *								<li>a number that indicates the response code from the server    
409 		 *                              <li>a string that contains any response text from the server
410 		 *                              </ol>
411 		 * 
412 		 */
413 		this.sendDisconnect = function(timeout, onSuccess, onFailure) {
414 			
415 			trace("Enter: Client.sendDisconnect");
416 
417 		    // POST /restmsg?close=int
418 			var requestUrl = url + "?close=" + timeout;
419 			trace("Request URL is " + requestUrl);
420 			
421 		    var xhrClose = new XMLHttpRequest();
422 		    xhrClose.open('POST', requestUrl, true);
423 		    this.setRequestHeaders(xhrClose);
424 
425 		    xhrClose.onreadystatechange = function() {
426 		    	if (this.readyState != 4)  { return; }
427 		    	if (this.status === 200) {
428 		    		if (onSuccess) {
429 		    			onSuccess(this.responseText);
430 		    		}
431 		    	} else {
432 		    		if (onFailure) {
433 		    			onFailure(this.status, this.responseText);
434 		    		}
435 		    	}
436 		    	this.onreadystatechange = function() { };
437 		    };
438 		    
439 		    xhrClose.send();
440 
441 		}
442 
443 	};
444 
445 
446 	// Module contents.
447 	return {
448 		Client: Client
449 	};
450 
451 })(window);
452