1 /******************************************************************************
  2  *
  3  *  Copyright 2014-2017 Paphus Solutions Inc.
  4  *
  5  *  Licensed under the Eclipse Public License, Version 1.0 (the "License");
  6  *  you may not use this file except in compliance with the License.
  7  *  You may obtain a copy of the License at
  8  *
  9  *      http://www.eclipse.org/legal/epl-v10.html
 10  *
 11  *  Unless required by applicable law or agreed to in writing, software
 12  *  distributed under the License is distributed on an "AS IS" BASIS,
 13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  *  See the License for the specific language governing permissions and
 15  *  limitations under the License.
 16  *
 17  ******************************************************************************/
 18 
 19 /**
 20  * Bot Libre open SDK.
 21  * This JavaScript SDK lets you access chat bot, live chat, chatroom, forum, script, graphic, user services on
 22  * the Bot Libre compatible websites, including:
 23  * - Bot Libre!
 24  * - Bot Libre for Business
 25  * - Live Chat libre!
 26  * - Forums libre!
 27  * 
 28  * This JavaScript script can be used directly, or copied/modified on your own website.
 29  * 
 30  * The SDK consist of two main class, SDKConnection and LiveChatConnection.
 31  * 
 32  * SDKConnection uses AJAX calls to provide access to the libre REST API.
 33  * This is used for chat bots, forums, user admin, and domains.
 34  * 
 35  * LiveChatConnection uses web sockets to provide access to live chat and chatrooms.
 36  * 
 37  * Version: 6.0.0-2017-10-16
 38  */
 39 
 40 /**
 41  * Static class for common util functions and static properties.
 42  * @class
 43  */
 44 var SDK = {};
 45 
 46 SDK.DOMAIN = "www.botlibre.com";
 47 SDK.NAME = "Bot Libre!";
 48 SDK.APP = "";
 49 
 50 SDK.DOMAIN = window.location.host;
 51 SDK.APP = "/botlibre";
 52 
 53 SDK.PATH = "/rest/api";
 54 SDK.MAX_FILE_UPLOAD = 5000000;
 55 
 56 SDK.host = SDK.DOMAIN;
 57 SDK.app = SDK.APP;
 58 SDK.scheme = 'https:' == document.location.protocol ? "https" : "http";
 59 SDK.url = SDK.scheme + "://" + SDK.DOMAIN + SDK.APP;
 60 SDK.rest = SDK.url + SDK.PATH;
 61 SDK.backlinkURL = SDK.url;
 62 SDK.backlink = true;
 63 SDK.commands = true;
 64 
 65 /**
 66  * You must set your application ID to use the SDK.
 67  * You can obtain your application ID from your user page.
 68  * @static
 69  */
 70 SDK.applicationId = null;
 71 
 72 /**
 73  * Set the active language code.
 74  * This is used for voice recognition.
 75  */
 76 SDK.lang = "en";
 77 
 78 /**
 79  * Enable debug logging.
 80  * @static
 81  */
 82 SDK.debug = false;
 83 
 84 /**
 85  * Escape and filter bot messages for HTML and JavaScript content for XSS security.
 86  * This prevents bots sending advanced HTML and JavaScript in their messages.
 87  * Set this to false to allow your bot to send JavaScript.
 88  * @static
 89  */
 90 SDK.secure = true;
 91 
 92 /**
 93  * Force avatars to enable or disable canvas for video (currently used only for Chrome and Firefox).
 94  * @static
 95  */
 96 SDK.useCanvas = null;
 97 
 98 /**
 99  * Force avatars to enable or disable video (currently disabled for Safari on iPhone).
100  * @static
101  */
102 SDK.useVideo = null;
103 
104 /**
105  * Attempt to fix grey mp4 video background (only used for Chrome).
106  * @static
107  */
108 SDK.fixBrightness = null;
109 
110 /**
111  * Attempt to fix an issue with Chrome not processing the CSS after correctly when the chat bubble resizes.
112  * @static
113  */
114 SDK.fixChromeResizeCSS = true;
115 
116 /**
117  * Set the error static field to trap or log any errors.
118  */
119 SDK.error = function(message) {
120 	console.log(message);
121 }
122 
123 /**
124  * Allow our native speech API to use the third party ResponsiveVoice API.
125  * You must create an account with ResponsiveVoice to use their API, see https://responsivevoice.com
126  * @static
127  */
128 SDK.responsiveVoice = false;
129 SDK.speechSynthesis = 'speechSynthesis' in window;
130 /**
131  * The speechRate can be set to change the native speech voice speed.
132  * It can range between 0.1 (lowest) and 10.0 (highest).
133  * 1.0 is the default rate for the current platform or voice.
134  * Other values act as a percentage relative to this, so for example 2.0 is twice as fast, 0.5 is half as fast.
135  */
136 SDK.speechRate = null;
137 /**
138  * The speechPitch can be set to change the native speech voice pitch.
139  * It can range between 0.0 (lowest) and 2.0 (highest), with 1.0 being the default pitch for the current platform or voice.
140  */
141 SDK.speechRate = null;
142 SDK.initResponsiveVoice = function() {
143 	if (!('responsiveVoice' in window)) {
144 		console.log("ResponsiveVoice missing, you must load its script first");
145 		return;
146 	}
147 	SDK.responsiveVoice = true;
148 	SDK.speechSynthesis = true;
149 }
150 if (!('SpeechSynthesisUtterance' in window)) {
151 	function SpeechSynthesisUtterance2(text) {
152 		this.text = text;
153 	}
154 }
155 
156 SDK.currentAudio = null;
157 SDK.recognition = null;
158 SDK.recognitionActive = false;
159 SDK.backgroundAudio = null;
160 SDK.currentBackgroundAudio = null;
161 SDK.timers = {};
162 /**
163  * Track if auto play of media is enabled in the browser (mobile Chrome/Safari)
164  * Enable or disable to force audio auto play.
165  */
166 SDK.canPlayAudio = null;
167 /**
168  * Track if auto play of media is enabled in the browser (mobile Chrome/Safari)
169  * Enable or disable to force video auto play.
170  */
171 SDK.canPlayVideo = true;
172 SDK.audio = null;
173 SDK.autoPlayActionAudio = null;
174 SDK.autoPlayBackgroundAudio = null;
175 SDK.autoPlayDelay = 2000;
176 /**
177  * Play the audio file given the url.
178  */
179 SDK.play = function(file, channelaudio) {
180 	SDK.pauseSpeechRecognition();
181 	var audio = null;
182 	if (SDK.audio != null) {
183 		audio = SDK.audio;
184 		audio.pause();
185 		audio.onended = null;
186 		audio.onpause = null;
187 		audio.oncanplay = null;
188 		audio.src = file;
189 	} else {
190 		audio = new Audio(file);
191 	}
192 	if (SDK.recognitionActive) {
193 		audio.onended = function() {
194 			SDK.startSpeechRecognition();
195 			if (channelaudio != false) {
196 				SDK.currentAudio = null;
197 			}
198 		};
199 	} else if (channelaudio != false) {
200 		audio.onended = function() {
201 			SDK.currentAudio = null;
202 		};
203 	}
204 	if (SDK.canPlayAudio != true) {
205 		if (!SDK.isMobile()) {
206 			SDK.canPlayAudio = true;
207 		} else {
208 			audio.onplaying = function() {
209 				SDK.canPlayAudio = true;
210 			};
211 		}
212 	}
213 	if (channelaudio == false) {
214 		audio.play();
215 	} else {
216 		if (SDK.currentAudio != null && !SDK.currentAudio.ended && !SDK.currentAudio.paused) {
217 			SDK.currentAudio.onpause = function() {
218 				SDK.currentAudio = audio;
219 				audio.play();
220 			};
221 			SDK.currentAudio.pause();
222 		} else {
223 			SDK.currentAudio = audio;
224 			audio.play();
225 		}
226 	}
227 	if (SDK.canPlayAudio == null) {
228 		SDK.canPlayAudio = false;
229 		/**
230 		 * This allows the audio to be initialized inside a button callback.
231 		 * This is require to auto play on mobile Chrome and Safari.
232 		 */
233 		setTimeout(function() {
234 			if (SDK.canPlayAudio == false) {
235 				SDK.initAudio = function() {
236 					SDK.audio = new Audio(file);
237 					SDK.currentAudio = SDK.audio;
238 					SDK.currentAudio.onended = function() {
239 						SDK.currentAudio = null;
240 					};
241 					SDK.canPlayAudio = true;
242 					SDK.audio.play();
243 					document.getElementById("sdkplaybutton").style.display = "none";
244 				}
245 				var body = document.body || document.getElementsByTagName('body')[0];
246 				var playButton = document.createElement('div');
247 				var html = "<div id='sdkplaybutton' style='position:fixed;bottom:32px;left:32px;z-index:164;'><img onclick='SDK.initAudio()' width='64' src='"
248 					+ SDK.url + "/images/playsound.png'/></div>"
249 				playButton.innerHTML = html;
250 				body.appendChild(playButton);
251 				setTimeout(function() {
252 					document.getElementById("sdkplaybutton").style.display = "none";
253 				}, 10000);
254 			}
255 		}, SDK.autoPlayDelay);
256 	}
257 	return audio;
258 }
259 
260 SDK.playChime = true;
261 /**
262  * Play the chime sound.
263  */
264 SDK.chime = function() {
265 	if (SDK.playChime) {
266 		this.play(SDK.url + '/chime.mp3');
267 		SDK.playChime = false;
268 		var timer = setInterval(function () {
269 			SDK.playChime = true;
270 			clearInterval(timer);
271 		}, 1000);
272 	}
273 }
274 
275 /**
276  * Convert the text to speech and play it either using the browser native TTS support, or as server generated an audio file.
277  * The voice is optional and can be any voice supported by the server (see the voice page for a list of voices).
278  * For native voices a language code can be given.
279  * If the browser supports TTS the native voice will be used by default.
280  */
281 SDK.tts = function(text, voice, native, lang, nativeVoice, mod) {
282 	try {
283 		if ((native || (native == null && voice == null)) && SDK.speechSynthesis) {
284 			var utterance = null;
285 			if ('SpeechSynthesisUtterance' in window) {
286 				utterance = new SpeechSynthesisUtterance(text);
287 			} else {
288 				utterance = new SpeechSynthesisUtterance2(text);
289 			}
290 			SDK.nativeTTS(utterance, lang, nativeVoice);
291 		} else {		
292 			var url = SDK.rest + '/form-speak?&text=';
293 			url = url + encodeURIComponent(text);
294 			if (voice != null) {
295 				url = url + '&voice=' + voice;
296 			}
297 			if (mod != null) {
298 				url = url + '&mod=' + mod;
299 			}
300 			if (SDK.applicationId != null) {
301 				url = url + '&application=' + SDK.applicationId;
302 			}
303 	
304 			var request = new XMLHttpRequest();
305 			var self = this;
306 			request.onreadystatechange = function() {
307 				if (request.readyState != 4) return;
308 				if (request.status != 200) {
309 					console.log('Error: Speech web request failed');
310 					return;
311 				}
312 				self.play(SDK.url + "/" + request.responseText);
313 			}
314 			
315 			request.open('GET', url, true);
316 			request.send();
317 		}
318 	} catch (error) {
319 		console.log('Error: Speech web request failed');
320 	}
321 }
322 
323 /**
324  * Use the ResponsiveVoice API.
325  */
326 SDK.responsiveVoiceTTS = function(utterance, lang, voice) {
327 	var events = {};
328 	try {
329 		SDK.pauseSpeechRecognition();
330 		if (voice == null || voice == "") {
331 			voice = "US English Female";
332 		}
333 		if (SDK.recognitionActive) {
334 			events.onend = function() {
335 				SDK.startSpeechRecognition();
336 			}
337 		}
338 		if (utterance.onend != null) {
339 			events.onend = utterance.onend;
340 		}
341 		if (utterance.onstart != null) {
342 			events.onstart = utterance.onstart;
343 		}
344 		responsiveVoice.speak(utterance.text, voice, events);
345 	} catch (error) {
346 		console.log(error);
347 	}
348 }
349 
350 /**
351  * Speak the native utterance first setting the voice and language.
352  */
353 SDK.nativeTTS = function(utterance, lang, voice) {
354 	if (SDK.speechRate != null) {
355 		utterance.rate = SDK.speechRate;
356 	}
357 	if (SDK.speechPitch != null) {
358 		utterance.pitch = SDK.speechPitch;
359 	}
360 	if (SDK.responsiveVoice) {
361 		SDK.responsiveVoiceTTS(utterance, lang, voice);
362 		return;
363 	}
364 	if (lang == null) {
365 		lang = SDK.lang;
366 	}
367 	SDK.pauseSpeechRecognition();
368 	if (SDK.recognitionActive) {
369 		utterance.addEventListener("end", function() {
370 			SDK.startSpeechRecognition();
371 		});
372 	}
373 	speechSynthesis.cancel();
374 	if (lang == null && voice == null) {
375 		// Events don't always get fired unless this is done...
376 		setTimeout(function() {
377 			speechSynthesis.speak(utterance);
378 		}, 100);
379 		return;
380 	}
381 	var voices = speechSynthesis.getVoices();
382 	var foundVoice = null;
383 	var foundLang = null;
384 	var spoken = false;
385 	if (voices.length == 0) {
386 		speechSynthesis.onvoiceschanged = function() {
387 			if (spoken) {
388 				return;
389 			}
390 			voices = speechSynthesis.getVoices();
391 	    	for (i = 0; i < voices.length; i++) {
392 	    		if (voice != null && (voice.length != 0) && voices[i].name.toLowerCase().indexOf(voice.toLowerCase()) != -1) {
393 	    			if (foundVoice == null || voices[i].name == voice) {
394 		    			foundVoice = voices[i];	    				
395 	    			}
396 	    		} else if (lang != null && (lang.length != 0) && voices[i].lang.toLowerCase().indexOf(lang.toLowerCase()) != -1) {
397 	    			if (foundLang == null || voices[i].lang == lang) {
398 	    				foundLang = voices[i];	    				
399 	    			}
400 	    		}
401 	    	}
402 	    	if (foundVoice != null) {
403 	    		utterance.voice = foundVoice;
404 	    	} else if (foundLang != null) {
405 	    		utterance.voice = foundLang;	    		
406 	    	}
407 	    	spoken = true;
408 			setTimeout(function() {
409 				speechSynthesis.speak(utterance);
410 			},100);
411 	    };
412 	} else {
413     	for (i = 0; i < voices.length; i++) {
414     		if (voice != null && (voice.length != 0) && voices[i].name.toLowerCase().indexOf(voice.toLowerCase()) != -1) {
415     			if (foundVoice == null || voices[i].name == voice) {
416 	    			foundVoice = voices[i];	    				
417     			}
418     		} else if (lang != null && (lang.length != 0) && voices[i].lang.toLowerCase().indexOf(lang.toLowerCase()) != -1) {
419     			if (foundLang == null || voices[i].lang == lang) {
420     				foundLang = voices[i];	    				
421     			}
422     		}
423     	}
424     	if (foundVoice != null) {
425     		utterance.voice = foundVoice;
426     	} else if (foundLang != null) {
427     		utterance.voice = foundLang;	    		
428     	}
429 		setTimeout(function() {
430 			speechSynthesis.speak(utterance);
431 		},100);
432 	}
433 }
434 
435 /**
436  * Allow text to be translated into another language is the interface elements.
437  */
438 SDK.translator = null;
439 SDK.translate = function(text) {
440 	if (SDK.translator == null) {
441 		SDK.translator = SDK.translators[SDK.lang];
442 		if (SDK.translator == null) {
443 			SDK.translator = {};
444 		}
445 	}
446 	var translated = SDK.translator[text];
447 	if (translated != null) {
448 		return translated;
449 	}
450 	return text
451 }
452 SDK.translators = {
453 	"pt" : {
454 		"Name" : "Nome",
455 		"Phone" : "Telemóvel",
456 		"Connect" : "Ligar"
457 	}
458 }
459 
460 /**
461  * Detect Chrome browser.
462  */
463 SDK.isChrome = function() {
464 	var agent = navigator.userAgent.toLowerCase()
465 	return agent.indexOf('chrome') != -1 && agent.indexOf('edge') == -1;
466 }
467 
468 /**
469  * Detect Firefox browser.
470  */
471 SDK.isFirefox = function() {
472 	return navigator.userAgent.toLowerCase().indexOf('firefox') != -1;
473 }
474 
475 /**
476  * Detect Safari browser.
477  */
478 SDK.isSafari = function() {
479 	return navigator.userAgent.toLowerCase().indexOf('safari') != -1;
480 }
481 
482 /**
483  * Detect mobile browser.
484  */
485 SDK.isMobile = function() {
486 	if (navigator.userAgent.match(/Android/i)
487 		 || navigator.userAgent.match(/webOS/i)
488 		 || navigator.userAgent.match(/iPhone/i)
489 		 || navigator.userAgent.match(/iPad/i)
490 		 || navigator.userAgent.match(/iPod/i)
491 		 || navigator.userAgent.match(/BlackBerry/i)
492 		 || navigator.userAgent.match(/Windows Phone/i)) {
493 		return true;
494 	} else {
495 		return false;
496 	}
497 }
498 
499 /**
500  * Detect iPhone OS.
501  */
502 SDK.isIPhone = function() {
503 	if (navigator.userAgent.match(/iPhone/i)) {
504 		return true;
505 	}
506 	return false;
507 }
508 
509 /**
510  * Detect Mac OS.
511  */
512 SDK.isMac = function() {
513 	return navigator.platform.toLowerCase().indexOf('mac') != -1;
514 }
515 
516 SDK.hd = false;
517 SDK.format = (SDK.isChrome() || SDK.isFirefox()) ? "webm" : "mp4";
518 // Safari displays HTML5 video very poorly on iPhone.
519 if (SDK.isSafari() && SDK.isIPhone()) {
520 	SDK.format = "img";
521 }
522 
523 /**
524  * Insert the text into the input field.
525  */
526 SDK.insertAtCaret = function(element, text) {
527     if (document.selection) {
528         element.focus();
529         var sel = document.selection.createRange();
530         sel.text = text;
531         element.focus();
532     } else if (element.selectionStart || element.selectionStart == 0) {
533         var startPos = element.selectionStart;
534         var endPos = element.selectionEnd;
535         var scrollTop = element.scrollTop;
536         element.value = element.value.substring(0, startPos) + text + element.value.substring(endPos, element.value.length);
537         element.focus();
538         element.selectionStart = startPos + text.length;
539         element.selectionEnd = startPos + text.length;
540         element.scrollTop = scrollTop;
541     } else {
542         element.value += text;
543         element.focus();
544     }
545 }
546 
547 /**
548  * Fix innerHTML for IE and Safari.
549  */
550 SDK.innerHTML = function(element) {
551 	var html = element.innerHTML;
552 	if (html == null) {
553 		var serializer = new XMLSerializer();
554 		html = "";
555 		for (var index = 0; index < element.childNodes.length; index++) {
556 			html = html + serializer.serializeToString(element.childNodes[index]);
557 		}
558 	}
559 	var index = html.indexOf("<");
560 	var index2 = html.indexOf(">")
561 	if (index != -1 && index2 > index) {
562 		html = html.replace(/</g, "<");
563 		html = html.replace(/>/g, ">");
564 	}
565 	if (html.indexOf("&") != -1) {
566 		html = html.replace(/&/g, "&");
567 	}
568 	return html;
569 }
570 
571 /**
572  * Strip HTML tags from text.
573  * Return plain text.
574  */
575 SDK.stripTags = function(html) {
576 	var element = document.createElement("p");
577 	element.innerHTML = html;
578 	SDK.removeTags(element);
579 	return element.innerText || element.textContent;
580 }
581 
582 SDK.removeTags = function(node) {
583 	if (node.className == 'nospeech' || node.tagName == 'SCRIPT' || node.tagName == 'SELECT' || node.tagName == 'BUTTON' || node.tagName == 'OPTION') {
584 		node.parentNode.removeChild(node);
585 	} else {
586 		var index = 0;
587 		var childNodes = node.childNodes;
588 		var children = [];
589 		while (index < childNodes.length) {
590 			children[index] = childNodes[index];
591 			index++;
592 		}
593 		var index = 0;
594 		while (index < children.length) {
595 			SDK.removeTags(children[index]);
596 			index++;
597 		}
598 	}
599 	return node;
600 }
601 
602 /**
603  * Replace reserved HTML character with their HTML escape codes.
604  */
605 SDK.escapeHTML = function(html) {
606 	return html.replace(/&/g, "&")
607     	.replace(/</g, "<")
608     	.replace(/>/g, ">")
609     	.replace(/"/g, """)
610     	.replace(/'/g, "'");
611 }
612 
613 /**
614  * Replace URL and email references in the text with HTML links.
615  */
616 SDK.linkURLs = function(text) {
617 	var http = text.indexOf("http") != -1;
618 	var www = text.indexOf("www.") != -1;
619 	var email = text.indexOf("@") != -1;
620 	if (!http && !www && !email) {
621 		return text;
622 	}
623 	if (text.indexOf("<") != -1 && text.indexOf(">") != -1) {
624 		return text;
625 	}
626 	if (http) {
627 	    var regex = /\b(?:https?|ftp|file):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;
628 	    text = text.replace(regex, function(url, b, c) {
629 	    	var lower = url.toLowerCase();
630 	    	if (lower.indexOf(".png") != -1 || lower.indexOf(".jpg") != -1 || lower.indexOf(".jpeg") != -1 || lower.indexOf(".gif") != -1) {
631 	    		return '<a href="' + url + '" target="_blank"><img src="' + url + '" height="50"></a>';
632 	    	} else if (lower.indexOf(".mp4") != -1 || lower.indexOf(".webm") != -1 || lower.indexOf(".ogg") != -1) {
633 	    		return '<a href="' + url + '" target="_blank"><video src="' + url + '" height="50"></a>';
634 	    	} else if (lower.indexOf(".wav") != -1 || lower.indexOf(".mp3") != -1) {
635 	    		return '<a href="' + url + '" target="_blank"><audio src="' + url + '" controls>audio</a>';
636 	    	} else {
637 	    		return '<a href="' + url + '" target="_blank">' + url + '</a>';
638 	    	}
639 	    });
640 	} else if (www) {
641 	    var regex = /((www\.)[^\s]+)/gim;
642 	    text = text.replace(regex, function(url, b, c) {
643 	        return '<a href="http://' + url + '" target="_blank">' + url + '</a>';
644 	    });
645 	}
646     
647     // http://, https://, ftp://
648     //var urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;
649 
650     // www. 
651     // var wwwPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
652 
653     // [email protected]
654 	if (email) {
655     	var emailPattern = /(([a-zA-Z0-9_\-\.]+)@[a-zA-Z_]+?(?:\.[a-zA-Z]{2,6}))+/gim;
656     	text = text.replace(emailPattern, '<a target="_blank" href="mailto:$1">$1</a>');
657 	}
658 	return text;
659 }
660 
661 /**
662  * Enable speech recognition if supported by the browser, and insert the voice to text to the input field.
663  * Optionally call click() on the button.
664  */
665 SDK.registerSpeechRecognition = function(input, button) {
666 	if (SDK.recognition == null) {
667 		if ('webkitSpeechRecognition' in window) {
668 			SDK.recognition = new webkitSpeechRecognition();
669 			if (SDK.lang != null) {
670 				SDK.recognition.lang = SDK.lang;
671 			}
672 			SDK.recognition.continuous = true;
673 			SDK.recognition.onresult = function (event) {
674 			    for (var i = event.resultIndex; i < event.results.length; ++i) {
675 			        if (event.results[i].isFinal) {
676 			        	SDK.insertAtCaret(input, event.results[i][0].transcript);	        	
677 			        }
678 			    }
679 			    if (button != null && button.click != null) {
680 			    	button.click();
681 				} else if (button != null) {
682 					button();
683 				}
684 			};
685 		} else {
686 			return;
687 		}
688 	}
689 }
690 
691 SDK.startSpeechRecognition = function() {
692 	if (SDK.recognition != null) {
693 		if (SDK.lang != null) {
694 			SDK.recognition.lang = SDK.lang;
695 		}
696 		SDK.recognition.start();
697 		SDK.recognitionActive = true;
698 	}
699 }
700 
701 SDK.pauseSpeechRecognition = function() {
702 	if (SDK.recognition != null) {
703 		SDK.recognition.stop();
704 	}
705 }
706 
707 SDK.stopSpeechRecognition = function() {
708 	if (SDK.recognition != null) {
709 		SDK.recognition.stop();
710 		SDK.recognitionActive = false;
711 	}
712 }
713 
714 SDK.popupwindow = function(url, title, w, h) {
715 	var left = (screen.width)-w-10;
716 	var top = (screen.height)-h-100;
717 	window.open(url, title, 'scrollbars=yes, resizable=yes, toolbar=no, location=no, directories=no, status=no, menubar=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
718 	return false;
719 }
720 
721 SDK.dataURLToBlob = function(dataURL) {
722     var marker = ';base64,';
723     if (dataURL.indexOf(marker) == -1) {
724         var parts = dataURL.split(',');
725         var contentType = parts[0].split(':')[1];
726         var raw = parts[1];
727 
728         return new Blob([raw], {type: contentType});
729     }
730 
731     var parts = dataURL.split(marker);
732     var contentType = parts[0].split(':')[1];
733     var raw = window.atob(parts[1]);
734     var rawLength = raw.length;
735 
736     var blobarray = new Uint8Array(rawLength);
737 
738     for (var i = 0; i < rawLength; ++i) {
739     	blobarray[i] = raw.charCodeAt(i);
740     }
741 
742     return new Blob([blobarray], {type: contentType});
743 }
744 
745 SDK.uploadImage = function(fileInput, url, width, height, properties, onFinish) {
746 	if (window.File && window.FileReader && window.FileList && window.Blob) {
747 		var files = fileInput.files;
748 		for (var i = 0; i < files.length; i++) {
749 			SDK.resizeAndUploadImage(files[i], url, width, height, properties, ((i == (files.length - 1) ? onFinish : null)))
750 		}
751 		return false;
752 	} else {
753 		alert('The File APIs are not fully supported in this browser.');
754 		return false;
755 	}
756 }
757 			
758 SDK.resizeAndUploadImage = function(file, url, width, height, properties, onFinish) {
759 	var reader = new FileReader();
760 	reader.onloadend = function() {
761 		var tempImg = new Image();
762 		tempImg.src = reader.result;
763 		tempImg.onload = function() {
764 			var MAX_WIDTH = width;
765 			var MAX_HEIGHT = height;
766 			if (width == null) {
767 				MAX_WIDTH = tempImg.width;
768 			}
769 			if (height == null) {
770 				MAX_HEIGHT = tempImg.height;
771 			}
772 			var tempW = tempImg.width;
773 			var tempH = tempImg.height;
774 			if (tempW > MAX_WIDTH) {
775 				 tempH *= MAX_WIDTH / tempW;
776 				 tempW = MAX_WIDTH;
777 			}
778 			if (tempH > MAX_HEIGHT) {
779 				 tempW *= MAX_HEIGHT / tempH;
780 				 tempH = MAX_HEIGHT;
781 			}
782 			var canvas = document.createElement('canvas');
783 			canvas.width = tempW;
784 			canvas.height = tempH;
785 			var ctx = canvas.getContext("2d");
786 			ctx.fillStyle = '#fff';
787 			ctx.fillRect(0, 0, canvas.width, canvas.height);
788 			ctx.drawImage(this, 0, 0, tempW, tempH);
789 			var dataUrl = canvas.toDataURL('image/jpeg');
790 			var blob = SDK.dataURLToBlob(dataUrl);
791 			var formData = new FormData();
792 			if (properties != null) {
793 				for (property in properties) {
794 					formData.append(property, properties[property]);
795 				}
796 			}
797 			formData.append('file', blob, file.name);
798 			var request = new XMLHttpRequest();
799 			request.onreadystatechange = function() {
800 				if (request.readyState != 4) {
801 					return;
802 				}
803 				if (onFinish != null) {
804 					onFinish();
805 				}
806 			}
807 			request.open("POST", url);
808 			request.send(formData);
809 		}
810  
811 	 }
812 	 reader.readAsDataURL(file);
813 }
814 
815 /**
816  * Open a JQuery error message dialog.
817  */
818 SDK.showError = function(message, title) {
819 	if (title == null) {
820 		title = "Error";
821 	}
822 	$("<div></div>").html(message).dialog({
823 		title: title,
824 		resizable: false,
825 		modal: true,
826 		buttons: {
827 			"Ok": function() {
828 				$(this).dialog("close");
829 			}
830 		}
831 	});
832 }
833 
834 /**
835  * Open a JQuery confirm dialog.
836  */
837 SDK.showConfirm = function(message, title, onYes, onNo) {
838 	if (title == null) {
839 		title = "Confirm";
840 	}
841 	$("<div></div>").html(message).dialog({
842 	    title: title,
843 	    resizable: false,
844 	    modal: true,
845 	    buttons: {
846 	        "Yes": function() {
847 	        	onYes();
848 	            $(this).dialog("close");
849 	        },
850 		    "No": function() {
851 		    	onNo();
852 		        $(this).dialog("close");
853 		    }
854 	    }
855 	});
856 }
857 
858 /**
859  * Evaluate any script tags in the node's descendants.
860  * This is required when innerHtml contains script nodes as they are not evaluated.
861  */
862 SDK.evalScripts = function(node) {
863     if (node.tagName == 'SCRIPT') {
864         var script  = document.createElement("script");
865         script.text = node.innerHTML;
866         for (var index = node.attributes.length-1; index >= 0; i--) {
867         	script.setAttribute(node.attributes[index].name, node.attributes[index].value);
868         }
869     	node.parentNode.replaceChild(script, node);
870     } else {
871         var index = 0;
872         var children = node.childNodes;
873         while (index < children.length) {
874         	SDK.evalScripts(children[index]);
875         	index++;
876         }
877     }
878     return node;
879 }
880 
881 /**
882  * Remove any script tags from the node.
883  */
884 SDK.removeScripts = function(node) {
885     if (node.tagName == 'SCRIPT') {
886         node.parentNode.removeChild(node);
887     } else {
888         var index = 0;
889         var children = node.childNodes;
890         while (index < children.length) {
891         	SDK.removeScripts(children[index]);
892         	index++;
893         }
894     }
895     return node;
896 }
897 
898 /**
899  * Add a stylesheet link to the page.
900  */
901 SDK.addStylesheet = function(fileName) {
902   var head = document.head;
903   var link = document.createElement('link');
904   link.type = 'text/css';
905   link.rel = 'stylesheet';
906   link.href = fileName;
907   head.appendChild(link);
908 }
909 
910 /**
911  * Add a style tag to the page.
912  */
913 SDK.addStyle = function(css) {
914 	var body = document.body || document.getElementsByTagName('body')[0];
915 	var style = document.createElement('style');
916 	style.type = 'text/css';
917 	if (style.styleSheet) {
918 		style.styleSheet.cssText = css;
919 	} else {
920 		style.appendChild(document.createTextNode(css));
921 	}
922 	body.appendChild(style);
923 }
924 
925 /**
926  * Graphics upload dialog and shared repositry browser.
927  * This provides a generic media upload dialog with many features:
928  * <ul>
929  *     <li>Upload dialog UI
930  *     <li>Locally resize images before upload
931  *     <li>Upload from a web URL
932  *     <li>Upload a media file from a shared graphics repository
933  * </ul>
934  * @class
935  */
936 function GraphicsUploader() {
937 	this.id = "graphics-browser";
938 	this.title = "Media Browser";
939 	this.browserClass = "dialog";
940 	this.dialogId = "graphics-uploader";
941 	this.dialogTitle = "Upload Media";
942 	this.dialogClass = "dialog";
943 	this.uploadURL = "upload-media";
944 	this.uploadFormProperties;
945 	this.reloadOnSubmit = true;
946 	this.fileInput;
947 	this.urlInput;
948 	this.prefix = "uploader-";
949 	this.renderedDialog = false;
950 	this.submit = true;
951 	this.showFile = true;
952 	this.showURL = true;
953 	this.showBrowse = true;
954 	this.sdk = null;
955 	this.url;	
956 	
957 	/**
958 	 * Open JQyery upload dialog.
959 	 */
960 	this.openUploadDialog = function() {
961 		if (!this.renderedDialog) {
962 			this.renderUploadDialog();
963 		}
964 		$( '#' + this.dialogId ).dialog("open");
965 	}
966 	
967 	/**
968 	 * Open JQyery browser dialog.
969 	 */
970 	this.openBrowser = function() {
971 		var browser = document.getElementById(this.id);
972 		if (browser != null) {
973 			$( '#' + this.id ).remove();
974 		}
975 		this.renderBrowser();
976 		$( '#' + this.id ).dialog("open");
977 		this.fetchMedia();
978 	}
979 
980 	/**
981 	 * Render JQyery upload dialog.
982 	 */
983 	this.renderUploadDialog = function() {
984 		var uploadDialog = document.createElement('div');
985 		uploadDialog.setAttribute('id', this.dialogId);
986 		uploadDialog.setAttribute('title', this.dialogTitle);
987 		uploadDialog.setAttribute('class', this.dialogClass);
988 		uploadDialog.style.display = "none";
989 		var html =
990 				"<style>\n"
991 				+ "." + this.prefix + "button { text-decoration:none; padding: 12px 2px 12px 2px; }\n"
992 				+ "." + this.prefix + "dialog-div { margin-top: 10px;margin-bottom: 10px; }\n"
993 				+ "</style>\n";
994 		if (this.showBrowse) {
995 			html = html
996 				+ "<div class='" + this.prefix + "dialog-div'>\n"
997 					+ "<a id='" + this.prefix + "browse-library' onclick='return false;' href='#' class='" + this.prefix + "button' title='Browse our shared media library'>\n"
998 					+ "<img src='images/importr.png' style='vertical-align: middle'>\n"
999 					+ "Browse media library\n"
1000 					+ "</a>\n"
1001 				+ "</div>\n";
1002 		}
1003 		if (this.showFile) {
1004 			if (this.showBrowse) {
1005 				html = html + "<hr>\n";
1006 			}
1007 			html = html
1008 				+ "<div class='" + this.prefix + "dialog-div'>\n"
1009 					+ "<a id='" + this.prefix + "upload-media' onclick='return false;' href='#' class='" + this.prefix + "button' title='Upload an image or media file from your computer or device'>\n"
1010 					+ "<img src='images/upload.png' style='vertical-align: middle'>\n"
1011 					+ "Upload from computer or device</a>\n"
1012 				+ "</div>\n"
1013 				+ "<div class='" + this.prefix + "dialog-div'>\n"
1014 					+ "<input id='" + this.prefix + "file-input' style='display:none' type='file' name='file' style='display:none'/>\n"
1015 					+ "<input id='" + this.prefix + "resize' type='checkbox' title='Resize the image file locally to the max pixel width, to srink large images, and save upload bandwidth (only use on image files)'>\n"
1016 					+ "Resize to <input id='" + this.prefix + "resize-width' type='number' value='300' style='width:50px;height:25px' title='Image resize width in pixels'> pixels\n"
1017 				+ "</div>\n";
1018 		}
1019 		if (this.showURL) {
1020 			if (this.showFile || this.showBrowse) {
1021 				html = html + "<hr>\n";
1022 			}
1023 			html = html
1024 				+ "<div class='" + this.prefix + "dialog-div'>\n"
1025 					+ "<a id='" + this.prefix + "upload-url' onclick='return false;' href='#' class='" + this.prefix + "button' title='Import an image or media file from the web URL'>\n"
1026 					+ "<img src='images/importr.png' style='vertical-align: middle'>\n"
1027 					+ "Import from web URL\n"
1028 					+ "</a>\n"
1029 				+ "</div>\n"
1030 				+ "<input id='" + this.prefix + "url-input' type='text' style='width:100%'>\n";
1031 		}
1032 		uploadDialog.innerHTML = html;
1033 		document.body.appendChild(uploadDialog);
1034 		
1035 		var self = this;
1036 		var element = document.getElementById(this.prefix + "upload-media");
1037 		if (element != null) {
1038 			element.addEventListener("click", function(event) {
1039 				if (document.getElementById(self.prefix + 'resize').checked) {
1040 					document.getElementById(self.prefix + 'file-input').click();
1041 				} else {
1042 					self.fileInput.click();
1043 				}
1044 				return false;			
1045 			});
1046 		}
1047 		element = document.getElementById(this.prefix + "file-input");
1048 		if (element != null) {
1049 			element.addEventListener("change", function(event) {
1050 				var width = parseInt(document.getElementById(self.prefix + 'resize-width').value);
1051 				SDK.uploadImage(
1052 						document.getElementById(self.prefix + 'file-input'),
1053 						self.uploadURL,
1054 						width,
1055 						null,
1056 						self.uploadFormProperties,
1057 						function() {
1058 							if (self.reloadOnSubmit) {
1059 								location.reload();
1060 							}
1061 						});
1062 				return false;
1063 			});
1064 		}
1065 		element = document.getElementById(this.prefix + "upload-url");
1066 		if (element != null) {
1067 			element.addEventListener("click", function(event) {
1068 				self.urlInput.value = document.getElementById(self.prefix + "url-input").value;
1069 				if (self.submit) {
1070 					self.urlInput.form.submit();
1071 				}
1072 				return false;			
1073 			});
1074 		}
1075 		element = document.getElementById(this.prefix + "browse-library");
1076 		if (element != null) {
1077 			element.addEventListener("click", function(event) {
1078 				self.openBrowser(function(url) {
1079 					if (url == null) {
1080 						return false;					
1081 					}
1082 					self.urlInput.value = url;
1083 					if (self.submit) {
1084 						self.urlInput.form.submit();
1085 					}
1086 				});
1087 				return false;			
1088 			});
1089 		}
1090 		
1091 		$( '#' + this.dialogId ).dialog({
1092 			autoOpen: false,
1093 			modal: true,
1094 		    buttons: {
1095 		        "Cancel": function() {
1096 		            $(this).dialog("close");
1097 		        }
1098 		    }
1099 		});
1100 		this.renderedDialog = true;
1101 	}
1102 	
1103 	/**
1104 	 * Render JQyery browser dialog.
1105 	 */
1106 	this.renderBrowser = function() {
1107 		var browser = document.createElement('div');
1108 		browser.setAttribute('id', this.id);
1109 		browser.setAttribute('title', this.title);
1110 		browser.setAttribute('class', this.browserClass);
1111 		browser.style.display = "none";
1112 
1113 		var self = this;
1114 		GraphicsUploader.updateSearch = function() {
1115 			self.fetchMedia();
1116 		}
1117 		var height = window.innerHeight - (window.innerHeight * 0.2);
1118 		var width = window.innerWidth - (window.innerWidth * 0.2);
1119 		var html =
1120 				"<style>\n"
1121 				+ "." + this.prefix + "button { text-decoration:none; padding: 12px 2px 12px 2px; }\n"
1122 				+ "." + this.prefix + "browser-div { }\n"
1123 				+ "." + this.prefix + "search-div { width:264px;margin:2px;display:inline-block;font-size:13px; }\n"
1124 				+ "." + this.prefix + "search-span { display:inline-block;width:78px; }\n"
1125 				+ "." + this.prefix + "browse-categories, ." + this.prefix + "browse-tags, , ." + this.prefix + "browse-filter { width:150px; }\n"
1126 				+ "." + this.prefix + "browse-sort { width:150px; }\n"
1127 				+ "." + this.prefix + "browse-div { display:inline-block;margin:2px;vertical-align:top; }\n"
1128 				+ "." + this.prefix + "browse-details { font-size:12px;color:grey; }\n"
1129 				+ "." + this.prefix + "browse-img { max-width:100px;max-height:100px; }\n"
1130 				+ "." + this.prefix + "browse-span div { position:absolute;margin:-1px 0 0 0;padding:3px 3px 3px 3px;background:#fff;border-style:solid;border-color:black;border-width:1px;max-width:300px;min-width:100px;z-index:152;visibility:hidden;opacity:0;transition:visibility 0s linear 0.3s, opacity 0.3s linear; } \n"
1131 				+ "." + this.prefix + "browse-span:hover div { display:inline;visibility:visible;opacity:1;transition-delay:0.5s; }\n"
1132 				+ "</style>\n"
1133 				+ "<div><div class='" + this.prefix + "search-div'><span class='" + this.prefix + "search-span'>Categories</span><input id='" + this.prefix + "browse-categories' type='text'/></div>"
1134 				+ " <div class='" + this.prefix + "search-div'><span class='" + this.prefix + "search-span'>Tags</span><input id='" + this.prefix + "browse-tags' type='text'/></div>"
1135 				+ " <div class='" + this.prefix + "search-div'><span class='" + this.prefix + "search-span'>Filter</span><input id='" + this.prefix + "browse-filter' type='text'/></div>"
1136 				+ " <div class='" + this.prefix + "search-div'><span class='" + this.prefix + "search-span'>Sort</span><select id='" + this.prefix + "browse-sort' onchange='GraphicsUploader.updateSearch()'><option value='name'>name</option><option value='Date'>date</option><option value='thumbs up'>thumbs up</option>\n"
1137 				+ "<option value='thumbs down'>thumbs down</option><option value='Stars'>stars</option><option value='connects'>connects</option></select>\n"
1138 				+ "<a href='#' onclick='GraphicsUploader.updateSearch()' title='Search'><img src='images/inspect.png' style='vertical-align: middle'></a></div>\n"
1139 				+ "</div>\n"				
1140 				+ "<div id='" + this.prefix + "browser-div' class='" + this.prefix + "browser-div'>\n"
1141 				+ "</div>\n";
1142 		browser.innerHTML = html;
1143 		document.body.appendChild(browser);
1144 
1145 		var self = this;
1146 		SDK.debug = true;
1147 		if (this.sdk == null) {
1148 			this.sdk = new SDKConnection();
1149 		}
1150 		var autocompleteEvent = function(event) {
1151 			var self = this;
1152 			$(self).autocomplete('search', '');
1153 		}
1154 		if (GraphicsUploader.tags.length == 0) {
1155 			var contentConfig = new ContentConfig();
1156 			contentConfig.type = "Graphic";
1157 			this.sdk.fetchTags(contentConfig, function(results) {
1158 				GraphicsUploader.tags = results;
1159 				$( "#" + self.prefix + "browse-tags" ).autocomplete({ source: GraphicsUploader.tags, minLength: 0, appendTo: $("#" + self.prefix + "browse-tags").parent() }).on('focus', autocompleteEvent);
1160 			});
1161 		} else {
1162 			$( "#" + this.prefix + "browse-tags" ).autocomplete({ source: GraphicsUploader.tags, minLength: 0, appendTo: $("#" + self.prefix + "browse-tags").parent() }).on('focus', autocompleteEvent);
1163 		}
1164 		if (GraphicsUploader.categories.length == 0) {
1165 			var contentConfig = new ContentConfig();
1166 			contentConfig.type = "Graphic";
1167 			this.sdk.fetchCategories(contentConfig, function(results) {
1168 				GraphicsUploader.categories = results;
1169 				$( "#" + self.prefix + "browse-categories" ).autocomplete({ source: GraphicsUploader.categories, minLength: 0, appendTo: $("#" + self.prefix + "browse-categories").parent() }).on('focus', autocompleteEvent);
1170 			});
1171 		} else {
1172 			$( "#" + this.prefix + "browse-categories" ).autocomplete({ source: GraphicsUploader.categories, minLength: 0, appendTo: $("#" + self.prefix + "browse-categories").parent() }).on('focus', autocompleteEvent);
1173 		}
1174 		var keyPressed = function search(e) {
1175 		    if (e.keyCode == 13) {
1176 		    	self.fetchMedia();
1177 		    }
1178 		}
1179 		$( "#" + this.prefix + "browse-tags" ).on("keydown", keyPressed);
1180 		$( "#" + this.prefix + "browse-categories" ).on("keydown", keyPressed);
1181 		$( "#" + this.prefix + "browse-filter" ).on("keydown", keyPressed);
1182 		
1183 		$( '#' + this.id ).dialog({
1184 			autoOpen: false,
1185 			modal: true,
1186             height: height,
1187             width: width,
1188 		    buttons: {
1189 		        "Cancel": function() {
1190 		            $(this).dialog("close");
1191 		        }
1192 		    }
1193 		});
1194 	}
1195 	
1196 	/**
1197 	 * Query and display graphics.
1198 	 */
1199 	this.fetchMedia = function() {
1200 		var browseConfig = new BrowseConfig();
1201 		browseConfig.type = "Graphic";
1202 		browseConfig.category = document.getElementById(this.prefix + 'browse-categories').value;
1203 		browseConfig.tag = document.getElementById(this.prefix + 'browse-tags').value;
1204 		browseConfig.filter = document.getElementById(this.prefix + 'browse-filter').value;
1205 		browseConfig.sort = document.getElementById(this.prefix + 'browse-sort').value;
1206 		var self = this;
1207 		var urlprefix = self.sdk.credentials.url + "/";
1208 		GraphicsUploader.chooseMedia = function(id) {
1209 			var config = new GraphicConfig();
1210 			config.id = id;
1211 			self.sdk.fetch(config, function(result) {
1212 				self.url = urlprefix + result.media;
1213 				if (self.urlInput != null) {
1214 					self.urlInput.value = self.url;
1215 					if (self.submit) {
1216 						self.urlInput.form.submit();
1217 					}
1218 				}
1219 			});
1220 		}
1221 		this.sdk.browse(browseConfig, function(results) {
1222 			var div = document.getElementById(self.prefix + "browser-div");
1223 			while (div.firstChild) {
1224 				div.removeChild(div.firstChild);
1225 			}
1226 			for (var index = 0; index < results.length; index++) {
1227 				var result = results[index];
1228 				var graphicDiv = document.createElement('div');
1229 				graphicDiv.setAttribute('id', self.prefix + 'browse-div');
1230 				graphicDiv.setAttribute('class', self.prefix + 'browse-div');
1231 				var html =
1232 					"<span id='" + self.prefix + "browse-span' class='" + self.prefix + "browse-span'>"
1233 					+ "<table style='border-style:solid;border-color:grey;border-width:1px'><tr><td style='height:100px;width:100px;' align='center' valign='middle'>"
1234 					+ "<a href='#' onclick='GraphicsUploader.chooseMedia(" + result.id + ")'><img id='" + self.prefix + "browse-img' class='" + self.prefix + "browse-img' src='" + urlprefix + result.avatar + "'></a>\n"
1235 					+ "</td></tr></table>"
1236 					+ "<div>"
1237 					+ "<span><b>" + result.name + "</b><br/>" + result.description + "</span><br/>"
1238 					+ "<span id='" + self.prefix + "browse-details' class='" + self.prefix + "browse-details'>";
1239 				if (result.categories != null && result.categories != "") {
1240 					html = html + "Categories: " + result.categories + "<br/>";
1241 				}
1242 				if (result.tags != null && result.tags != "") {
1243 					html = html + "Tags: " + result.tags + "<br/>";
1244 				}
1245 				if (result.license != null && result.license != "") {
1246 					html = html + "License: " + result.license + "<br/>";
1247 				}
1248 				html = html
1249 					+ "</div>"
1250 					+ "</span>\n"
1251 					+ "<div style='max-width:100px'><a href='#' style='text-decoration:none;' onclick='GraphicsUploader.chooseMedia(" + result.id + ")'><span id='" + self.prefix + "browse-details' class='" + self.prefix + "browse-details'>" + result.name + "</span></div></a>\n";
1252 				graphicDiv.innerHTML = html;
1253 				var img = document.createElement('img');
1254 				img.setAttribute('src', urlprefix + result.avatar);
1255 				div.appendChild(graphicDiv);
1256 			}
1257 		});
1258 	}
1259 }
1260 
1261 GraphicsUploader.map = {};
1262 
1263 GraphicsUploader.tags = [];
1264 GraphicsUploader.categories = [];
1265 
1266 /**
1267  * Open a media uploader dialog initialized with a form.
1268  * The dialog will use the form's action to upload media as 'file' for a file, or 'upload-url' for a URL.
1269  * The form should define an ID if to be used on multiple forms in the same document.
1270  * This can be used on the onclick on an input element to open the dialog i.e. <input type="submit" onclick="return GraphicsUploader.openUploadDialog(this.form)" value="Upload">
1271  * This will create a hidden input of type file ('file'), and a hidden input of type text ('upload-url'), these will be passed to your server when the dialog submits the form.
1272  */
1273 GraphicsUploader.openUploadDialog = function(form, title, showFile, showURL, showBrowse) {    	
1274 	var id = form.getAttribute('id');
1275 	var prefix = "uploader-";
1276 	var dialogId = "graphics-uploader";
1277 	var browserId = "graphics-browser";
1278 	if (id == null) {
1279 		id = "uploader";
1280 	} else {
1281 		prefix = id + '-' + prefix;
1282 		dialogId = id + '-' + dialogId;
1283 		browserId = id + '-' + browserId;
1284 	}
1285 	var uploader = GraphicsUploader.map[id];
1286 	if (uploader == null) {
1287 		uploader = new GraphicsUploader();
1288 		GraphicsUploader.map[id] = uploader;
1289 		var div = document.createElement('div');
1290 		var html =
1291 			"<input id='" + id + "file-input' style='display:none' onchange='this.form.submit()' type='file' name='file'/>\n"
1292 			+ "<input id='" + id + "url-input' style='display:none' name='upload-url' type='text'>";
1293 		div.innerHTML = html;
1294 		form.appendChild(div);
1295 		if (title != null) {
1296 			uploader.dialogTitle = title;
1297 		}
1298 		if (showFile != null) {
1299 			uploader.showFile = showFile;
1300 		}
1301 		if (showURL != null) {
1302 			uploader.showURL = showURL;
1303 		}
1304 		if (showBrowse != null) {
1305 			uploader.showBrowse = showBrowse;
1306 		}
1307 		uploader.prefix = prefix;
1308 		uploader.dialogId = dialogId;
1309 		uploader.id= browserId;
1310 		uploader.fileInput = document.getElementById(id + 'file-input');
1311 		uploader.urlInput = document.getElementById(id + 'url-input');
1312 		uploader.uploadURL = form.action;
1313 	}
1314 	uploader.openUploadDialog();
1315 	return false;
1316 }
1317 
1318 /**
1319  * Credentials used to establish a connection.
1320  * Defines the url, host, app, rest, which are all defaulted and should not need to be changed,
1321  * Requires an application id.
1322  * You can obtain your application id from your user details page on the hosting website.
1323  * @class
1324  */
1325 function Credentials() {
1326 	this.host = SDK.host;
1327 	this.app = SDK.app;
1328 	this.url = SDK.url;
1329 	this.rest = SDK.rest;
1330 	this.applicationId = SDK.applicationId;
1331 }
1332 
1333 /**
1334  * Credentials for use with hosted services on the BOT libre website, a free bot hosting service.
1335  * http://www.botlibre.com
1336  * @class
1337  */
1338 function BOTlibreCredentials()  {
1339 	this.DOMAIN = "www.botlibre.com";
1340 	this.APP = "";
1341 	this.PATH = "/rest/api";
1342 	
1343 	this.host = this.DOMAIN;
1344 	this.app = this.APP;
1345 	this.url = "http://" + this.DOMAIN + this.APP;
1346 	this.rest = this.url + this.PATH;
1347 	this.applicationId = SDK.applicationId;
1348 }
1349 
1350 /**
1351  * Credentials for use with hosted services on the Bot Libre for Business website,
1352  * a commercial bot, live chat, chatroom, and forum, hosting service.
1353  * https://www.botlibre.biz
1354  * @class
1355  */
1356 function PaphusCredentials()  {
1357 	this.DOMAIN = "www.botlibre.biz";
1358 	this.APP = "";
1359 	this.PATH = "/rest/api";
1360 	
1361 	this.host = this.DOMAIN;
1362 	this.app = this.APP;
1363 	this.url = "http://" + this.DOMAIN + this.APP;
1364 	this.rest = this.url + this.PATH;
1365 	this.applicationId = SDK.applicationId;
1366 }
1367 
1368 /**
1369  * Credentials for use with hosted services on the LIVE CHAT libre website, a free live chat, chatrooms, forum, and chat bots that learn.
1370  * http://www.livechatlibre.com
1371  * @class
1372  */
1373 function LIVECHATlibreCredentials()  {
1374 	this.DOMAIN = "www.livechatlibre.com";
1375 	this.APP = "";
1376 	this.PATH = "/rest/api";
1377 	
1378 	this.host = this.DOMAIN;
1379 	this.app = this.APP;
1380 	this.url = "http://" + this.DOMAIN + this.APP;
1381 	this.rest = this.url + this.PATH;
1382 	this.applicationId = SDK.applicationId;
1383 }
1384 
1385 /**
1386  * Credentials for use with hosted services on the FORUMS libre website, a free embeddable forum hosting service.
1387  * http://www.forumslibre.com
1388  * @class
1389  */
1390 function FORUMSlibreCredentials()  {
1391 	this.DOMAIN = "www.forumslibre.com";
1392 	this.APP = "";
1393 	this.PATH = "/rest/api";
1394 	
1395 	this.host = this.DOMAIN;
1396 	this.app = this.APP;
1397 	this.url = "http://" + this.DOMAIN + this.APP;
1398 	this.rest = this.url + this.PATH;
1399 	this.applicationId = SDK.applicationId;
1400 }
1401 
1402 /**
1403  * Listener interface for a LiveChatConnection.
1404  * This gives asynchronous notification when a channel receives a message, or notice.
1405  * @class
1406  */
1407 function LiveChatListener() {
1408 	/**
1409 	 * A user message was received from the channel.
1410 	 */
1411 	this.message = function(message) {};
1412 	
1413 	/**
1414 	 * An informational message was received from the channel.
1415 	 * Such as a new user joined, private request, etc.
1416 	 */	
1417 	this.info = function(message) {};
1418 
1419 	/**
1420 	 * An error message was received from the channel.
1421 	 * This could be an access error, or message failure.
1422 	 */	
1423 	this.error = function(message) {};
1424 	
1425 	/**
1426 	 * Notification that the connection was closed.
1427 	 */
1428 	this.closed = function() {};
1429 	
1430 	/**
1431 	 * The channels users changed (user joined, left, etc.)
1432 	 * This contains a comma separated values (CSV) list of the current channel users.
1433 	 * It can be passed to the SDKConnection.getUsers() API to obtain the UserConfig info for the users.
1434 	 */
1435 	this.updateUsers = function(usersCSV) {};
1436 
1437 	/**
1438 	 * The channels users changed (user joined, left, etc.)
1439 	 * This contains a HTML list of the current channel users.
1440 	 * It can be inserted into an HTML document to display the users.
1441 	 */
1442 	this.updateUsersXML = function(usersXML) {};
1443 }
1444 
1445 /**
1446  * The WebLiveChatListener provides an integration between a LiveChatConnection and an HTML document.
1447  * It updates the document to message received from the connection, and sends messages from the document's form.
1448  * The HTML document requires the following elements:
1449  * - chat - <input type='text'> chat text input for sending messages
1450  * - send - <input type='submit'> button for sending chat input
1451  * - response - <p> paragraph for last chat message
1452  * - console - <table> table for chat log, and user log
1453  * - scroller - <div> div for chat log scroll pane
1454  * - online - <table> table for chat user list
1455  * @class
1456  */
1457 function WebLiveChatListener() {
1458 	/** Set the caption for the button bar button. */
1459 	this.caption = null;
1460 	this.switchText = true;
1461 	this.playChime = true;
1462 	this.speak = false;
1463 	this.voice = null;
1464 	this.nativeVoice = null;
1465 	this.nativeVoiceName = null;
1466 	this.lang = null;
1467 	this.nick = "";
1468 	this.connection = null;
1469 	this.sdk = null;
1470 	/** Configure if chat should be given focus after message. */
1471 	this.focus = true;
1472 	/** Element id and class prefix. Can be used to have multiple avatars in the same page, or avoid naming collisions. */
1473 	this.prefix = "";
1474 	/** Allow the chat box button color to be set. */
1475 	this.color = "#009900";
1476 	/** Allow the user to modify style sheets. */
1477 	this.version = null;
1478 	/** Allow the chat box hover button color to be set. */
1479 	this.hoverColor = "grey";
1480 	/** Allow the chat box background color to be set. */
1481 	this.background = null;
1482 	/** Chat box width. */
1483 	this.width = 300;
1484 	/** Chat box height. */
1485 	this.height = 320;
1486 	/** Chat box offset from side. */
1487 	this.offset = 30;
1488 	/** Chat Button Vertial Offset*/
1489 	this.verticalOffset = 0;
1490 	/** Print response in chat bubble. */
1491 	this.bubble = false;
1492 	/** Set the location of the button and box, one of "bottom-right", "bottom-left", "top-right", "top-left". */
1493 	this.boxLocation = "bottom-right";
1494 	/** Set styles explicitly to avoid inheriting page styles. Disable this to be able to override styles. */
1495 	this.forceStyles = true;
1496 	/** Override the URL used in the chat box popup. */
1497 	this.popupURL = null;
1498 	/** Set if the box chat log should be shown. */
1499 	this.chatLog = true;
1500 	/** Box chat loading message to display. */
1501 	this.loading = "loading...";
1502 	/** Box chat show online users option. */
1503 	this.online = false;
1504 	/** Link to online user list users to their profile page. */
1505 	this.linkUsers = true;
1506 	/** Configure message log format (table or log). */
1507 	this.chatLogType = "log";
1508 	/** Configure the chat to auto accept a bot after waiting */
1509 	this.autoAccept = null;
1510 	/** Prompt for name/email before connecting. */
1511 	this.promptContactInfo = false;
1512 	this.hasContactInfo = false;
1513 	/** Set if the back link should be displayed. */
1514 	this.backlink = SDK.backlink;
1515 	this.contactName = null;
1516 	this.contactEmail = null;
1517 	this.contactPhone = null;
1518 	this.contactInfo = "";
1519 	/** Allow the close button on the box button bar to be removed, and maximize on any click to the button bar. */
1520 	this.showClose = true;
1521 	/** Provide an email chat log menu item. */
1522 	this.emailChatLog = false;
1523 	/** Ask the user if they want an email of the chat log on close. */
1524 	this.promptEmailChatLog = false;
1525 	this.windowTitle = document.title;
1526 	this.isActive = true;
1527 	/** Variables used to get the user and bot images. */
1528 	this.botThumb = {};
1529 	this.userThumb = {};
1530 	self = this;
1531 	/** JSON object of all currently logged in users in the chat room take from updateUsersXML. */
1532 	this.users = {};
1533 	/** Box chat chat room option. */
1534 	this.chatroom = false;
1535 	/** Show and hides menu bar */
1536 	this.showMenubar = true;
1537 	/** Show Box Max */
1538 	this.showBoxmax = true;
1539 	/** Show Send Image */
1540 	this.showSendImage = true;
1541 	
1542 	/**
1543 	 * Create an embedding bar and div in the current web page.
1544 	 */
1545 	this.createBox = function() {
1546 		if (this.prefix == "" && this.elementPrefix != null) {
1547 			this.prefix = this.elementPrefix;
1548 		}
1549 		if (this.caption == null) {
1550 			this.caption = this.instanceName;
1551 		}
1552 		var backgroundstyle = "";
1553 		var buttonstyle = "";
1554 		var buttonHoverStyle = "";
1555 		var hidden = "hidden";
1556 		var border = "";
1557 		if (this.backgroundIfNotChrome && SDK.isChrome()) {
1558 			this.background = null;
1559 		}
1560 		if (this.background != null) {
1561 			backgroundstyle = " style='background-color:" + this.background + "'";
1562 			hidden = "visible";
1563 			border = "border:1px;border-style:solid;border-color:black;";
1564 		}
1565 		if (this.color != null) {
1566 			buttonstyle = "background-color:" + this.color + ";";
1567 		}
1568 		if (this.hoverColor != null) {
1569 			buttonHoverStyle = "background-color:" + this.hoverColor + ";";
1570 		}
1571 		
1572 		var minWidth = "";
1573 		var divWidth = "";
1574 		var background = "";
1575 		var minHeight = "";
1576 		var divHeight = "";
1577 		var maxDivWidth = "";
1578 		if (this.width != null) {
1579 			minWidth = "width:" + this.width + "px;";
1580 			background = "background-size:" + this.width + "px auto;";
1581 			divWidth = minWidth;
1582 			divHeight = "min-height:" + this.width + "px;";
1583 			responseWidth = "width:" + (this.width - 32) + "px;";
1584 			maxDivWidth = "max-width:" + (this.width - 50) + "px;";
1585 		}
1586 		if (this.height != null) {
1587 			minHeight = "height:" + this.height + "px;";
1588 			divHeight = minHeight;
1589 			if (this.width != null) {
1590 				background = "background-size:" + this.width + "px " + this.height + "px;";
1591 			} else {
1592 				background = "background-size: auto " + this.height + "px;";
1593 				divWidth = "min-width:" + this.height + "px;";
1594 			}
1595 		}
1596 		var boxloc = "bottom:10px;right:10px";
1597         if (this.boxLocation == "top-left") {
1598             boxloc = "top:10px;left:10px";
1599         } else if (this.boxLocation == "top-right") {
1600             boxloc = "top:10px;right:10px";
1601         } else if (this.boxLocation == "bottom-left") {
1602             boxloc = "bottom:10px;left:10px";
1603         } else if (this.boxLocation == "bottom-right") {
1604             boxloc = "bottom:10px;right:10px";
1605         }
1606         var locationBottom = 20;
1607         if (this.version < 6.0 || this.prefix != "botplatformchat") {
1608         	locationBottom = 2;
1609         }
1610         var boxbarloc = "bottom:" + (locationBottom + this.verticalOffset) + "px;right:" + this.offset + "px";
1611         if (this.boxLocation == "top-left") {
1612             boxbarloc = "top:" + (locationBottom + this.verticalOffset) + "px;left:" + this.offset + "px";
1613         } else if (this.boxLocation == "top-right") {
1614             boxbarloc = "top:" + (locationBottom + this.verticalOffset) + "px;right:" + this.offset + "px";
1615         } else if (this.boxLocation == "bottom-left") {
1616             boxbarloc = "bottom:" + (locationBottom + this.verticalOffset) + "px;left:" + this.offset + "px";
1617         } else if (this.boxLocation == "bottom-right") {
1618             boxbarloc = "bottom:" + (locationBottom + this.verticalOffset) + "px;right:" + this.offset + "px";
1619         }
1620 		var box = document.createElement('div');
1621 		var html =
1622 			"<style>\n"
1623 				+ "." + this.prefix + "box { position:fixed;" + boxloc + ";z-index:152;margin:2px;display:none;" + border + " }\n"
1624 				+ "." + this.prefix + "boxmenu { visibility:" + hidden + "; }\n" //margin-bottom:12px;
1625 				+ (this.forceStyles ? "#" : ".") + "" + this.prefix + "boxbarmax { font-size:18px;margin:2px;padding:0px;text-decoration:none; }\n"
1626 				+ "." + this.prefix + "boxbar { position:fixed;" + boxbarloc + ";z-index:152;margin:0;padding:6px;" + buttonstyle + " }\n"
1627 				+ "." + this.prefix + "boxbar:hover { " + buttonHoverStyle + " }\n"
1628 				+ "#" + this.prefix + "emailchatlogdialog { " + minWidth + " }\n"
1629 				+ "#" + this.prefix + "contactinfo { " + minHeight + minWidth + " }\n"
1630 				+ "." + this.prefix + "contactconnect { margin:4px;padding:8px;color:white;text-decoration:none;" + buttonstyle + " }\n"
1631 				+ "." + this.prefix + "no-bubble-text { " + responseWidth + "; max-height:100px; overflow:auto; }\n"
1632 				+ "." + this.prefix + "bubble-text { " + responseWidth + "; margin:4px; max-height:100px; overflow:auto; }\n"
1633 				+ (this.forceStyles ? "#" : ".") + this.prefix + "chat { width:99%;min-height:22px; }\n"
1634 				+ "." + this.prefix + "chat-1-div { " + maxDivWidth + "}\n"
1635 				+ "." + this.prefix + "chat-2-div { " + maxDivWidth + "}\n"
1636 				+ "." + this.prefix + "online-div { " + minWidth + " overflow-x: auto; overflow-y: hidden; white-space: nowrap; }\n"
1637 				+ "." + this.prefix + "scroller { overflow-x:hidden;" + minHeight + minWidth + " }\n"
1638 				+ "." + this.prefix + "box:hover ." + this.prefix + "boxmenu { visibility:visible; }\n";
1639 		if (this.version < 6.0 || this.prefix != "botplatformchat") {
1640 			html = html
1641 						+ "." + this.prefix + "box img { display:inline; }\n"
1642 						+ "." + this.prefix + "boxbar img { display:inline; }\n"
1643 						+ "." + this.prefix + "box:hover { border:1px;border-style:solid;border-color:black; }\n"
1644 						+ "." + this.prefix + "box:hover ." + this.prefix + "boxmenu { visibility:visible; }\n"
1645 						+ "." + this.prefix + "boxclose, ." + this.prefix + "boxmin, ." + this.prefix + "boxmax { font-size:22px;margin:2px;padding:0px;text-decoration:none; }\n"
1646 						+ "." + this.prefix + "boxclose:hover, ." + this.prefix + "boxmin:hover, ." + this.prefix + "boxmax:hover { color: #fff;background: grey; }\n"
1647 						+ "#" + this.prefix + "emailchatlogdialog span { margin-left:0px;margin-top:4px; }\n"
1648 						+ "#" + this.prefix + "emailchatlogdialog input { margin:4px;font-size:13px;height:33px;width:90%;border:1px solid #d5d5d5; }\n"
1649 						+ "." + this.prefix + "emailconfirm { margin:4px;padding:8px;color:white;background-color:grey;text-decoration:none; }\n"
1650 						+ "#" + this.prefix + "contactinfo span { margin-left:4px;margin-top:4px; }\n"
1651 						+ "#" + this.prefix + "contactinfo input { margin:4px;font-size:13px;height:33px;width:90%;border:1px solid #d5d5d5; }\n"
1652 						+ "." + this.prefix + "no-bubble { margin:4px; padding:8px; border:1px; border-style:solid; border-color:black; background-color:white; color:black; }\n"
1653 						+ "." + this.prefix + "boxbutton { width:20px;height:20px;margin:2px; }\n"
1654 						+ "." + this.prefix + "bubble-div { padding-bottom:15px;position:relative; }\n"
1655 						+ "." + this.prefix + "no-bubble-plain { margin:4px; padding:8px; border:1px; }\n"
1656 						+ "." + this.prefix + "bubble { margin:4px; padding:8px; border:1px; border-style:solid; border-color:black; border-radius:10px; background-color:white; color:black; }\n"
1657 						+ "." + this.prefix + "bubble:before { content:''; position:absolute; bottom:0px; left:40px; border-width:20px 0 0 20px; border-style:solid; border-color:black transparent; display:block; width:0;}\n"
1658 						+ "." + this.prefix + "bubble:after { content:''; position:absolute; bottom:3px; left:42px; border-width:18px 0 0 16px; border-style:solid; border-color:white transparent; display:block; width:0;}\n"
1659 						+ "." + this.prefix + "box-input-span { display:block; overflow:hidden; margin:4px; padding-right:4px; }\n"
1660 						+ "a." + this.prefix + "menuitem { text-decoration: none;display: block;color: #585858; }\n"
1661 						+ "a." + this.prefix + "menuitem:hover { color: #fff;background: grey; }\n"
1662 						+ "tr." + this.prefix + "menuitem:hover { background: grey; }\n"
1663 						+ "." + this.prefix + "powered { margin:4px;font-size:10px; }\n"
1664 						+ "img." + this.prefix + "menu { width: 24px;height: 24px;margin: 2px;cursor: pointer;vertical-align: middle; }\n"
1665 						+ "span." + this.prefix + "menu { color: #818181;font-size: 12px; }\n"
1666 						+ "img." + this.prefix + "toolbar { width: 25px;height: 25px;margin: 1px;padding: 1px;cursor: pointer;vertical-align: middle;border-style: solid;border-width: 1px;border-color: #fff; }\n"
1667 						+ "td." + this.prefix + "toolbar { width: 36px;height: 36px }\n"
1668 						/*+ "." + this.prefix + "online { height: 97px;width: 300px;overflow-x: auto;overflow-y: hidden;white-space: nowrap; }\n"*/
1669 						+ "." + this.prefix + "menupopup div { position:absolute;margin: -1px 0 0 0;padding: 3px 3px 3px 3px;background: #fff;border-style:solid;border-color:black;border-width:1px;width:160px;max-width:300px;z-index:152;visibility:hidden;opacity:0;transition:visibility 0s linear 0.3s, opacity 0.3s linear; }\n"
1670 						+ "." + this.prefix + "menupopup:hover div { display:inline;visibility:visible;opacity:1;transition-delay:0.5s; }\n"
1671 						+ "img.chat-user-thumb { height: 50px; }\n"
1672 						+ "a.user { text-decoration: none; }\n"
1673 						+ "td." + this.prefix + "chat-1 { width:100%;background-color: #d5d5d5;}\n"
1674 						+ "span." + this.prefix + "chat-1 { color:#333;}\n"
1675 						+ "span." + this.prefix + "chat-user { color:grey;font-size:small; }\n"
1676 						+ "." + this.prefix + "console { width:100%; }\n"
1677 						+ "." + this.prefix + "online-div { display: none; }\n"
1678 						+ "." + this.prefix + "-channel-title { display: none; }\n"
1679 						+ "#" + this.prefix + "boxtable { background:none; border:none; margin:0; }\n"
1680 						+ "#" + this.prefix + "boxbar3 { display:none; }\n"
1681 						+ "#" + this.prefix + "boxbarmax { color: white; }\n";
1682 		}
1683 		html = html
1684 			+ "</style>\n"
1685 			+ "<div id='" + this.prefix + "box' class='" + this.prefix + "box' " + backgroundstyle + ">"
1686 				+ "<div class='" + this.prefix + "boxmenu'>"
1687 					+ (this.backlink ? "<span class='" + this.prefix + "powered'>powered by <a href='" + SDK.backlinkURL + "' target='_blank'>" + SDK.NAME + "</a></span>" : "")
1688 					+ "<span class=" + this.prefix + "-channel-title>" + this.instanceName + "</span>"
1689 					+ "<span style='float:right'><a id='" + this.prefix + "boxmin' class='" + this.prefix + "boxmin' onclick='return false;' href='#'><img src='" + SDK.url + "/images/minimize.png'></a>";
1690 					if (this.showBoxmax) {
1691 						html = html + "<a id='" + this.prefix + "boxmax' class='" + this.prefix + "boxmax' onclick='return false;' href='#'><img src='" + SDK.url + "/images/open.png'></a></span><br/>";
1692 					} else {
1693 						html = html + "</span><br/>";
1694 					}	
1695 				html = html + "</div>";
1696 		if (this.online) {
1697 			html = html
1698         		+ "<div id='" + this.prefix + "online-div' class='" + this.prefix + "online-div'>"
1699         			+ "<div id='" + this.prefix + "online' class='" + this.prefix + "online'" + "style='display:none;'>"
1700         				+ "<table></table>"
1701         			+ "</div>"
1702         		+ "</div>";
1703 		}
1704 		if (this.chatLog) {
1705 			html = html
1706         		+ "<div id='" + this.prefix + "scroller' class='" + this.prefix + "scroller'>"
1707         		+ "<table id='" + this.prefix + "console' class='" + this.prefix + "console' width=100% cellspacing=2></table>"
1708         		+ "</div>"
1709 		}
1710 		var urlprefix = this.sdk.credentials.url + "/";
1711 		html = html
1712 				+ "<div>\n"
1713 					+ "<div " + (this.bubble ? "id='" + this.prefix + "bubble-div' class='" + this.prefix + "bubble-div'" : "") + ">"
1714 					+ "<div "
1715 					+ "id='" + this.prefix + (this.bubble ? "bubble" : (this.background == null ? "no-bubble" : "no-bubble-plain") )
1716 					+ "' class='" + this.prefix + (this.bubble ? "bubble" : (this.background == null ? "no-bubble" : "no-bubble-plain") )
1717 					+ "'><div id='" + this.prefix + (this.bubble ? "bubble-text" : "no-bubble-text" ) + "' "
1718 						+ "class='" + this.prefix + (this.bubble ? "bubble-text" : "no-bubble-text" ) + "'>"
1719 						+ "<span id='" + this.prefix + "response'>" + this.loading + "</span><br/>"
1720 					+ "</div></div></div>\n";
1721 		html = html
1722 			+ "<table id='" + this.prefix + "boxtable' class='" + this.prefix + "boxtable' style='width:100%'><tr>\n";
1723 			if (this.showMenubar) {
1724 				html = html
1725 					+ "<td class='" + this.prefix + "toolbar'><span class='" + this.prefix + "menu'>\n"
1726 					+ "<div style='inline-block;position:relative'>\n"
1727 					+ "<span id='" + this.prefix + "menupopup' class='" + this.prefix + "menupopup'>\n"
1728 					+ "<div style='text-align:left;bottom:30px'>\n"
1729 					+ "<table>\n"
1730 					+ "<tr class='" + this.prefix + "menuitem'>"
1731 					+ "<td><a id='" + this.prefix + "ping' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/empty.png' title='" + SDK.translate("Verify your connection to the server") + "'> " + SDK.translate("Ping server") + "</a></td>"
1732 					+ "</tr>\n";
1733 		    	if (this.chatroom) {
1734 				    html = html
1735 		        		+ "<tr class='" + this.prefix + "menuitem'>"
1736 		        			+ "<td><a id='" + this.prefix + "flag' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/flag2.png' title='" + SDK.translate("Flag a user for offensive content") + "'> " + SDK.translate("Flag user") + "</a></td>"
1737 		        		+ "</tr>\n"
1738 		        		+ "<tr class='" + this.prefix + "menuitem'>"
1739 		        			+ "<td><a id='" + this.prefix + "whisper' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/whisper.png' title='" + SDK.translate("Send a private message to another user") + "'> " + SDK.translate("Whisper user") + "</a></td>"
1740 		        		+ "</tr>\n"
1741 		        		+ "<tr class='" + this.prefix + "menuitem'>"
1742 		        			+ "<td><a id='" + this.prefix + "pvt' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/accept.png' title='" + SDK.translate("Invite another user to a private channel") + "'> " + SDK.translate("Request private") + "</a></td>"
1743 		        		+ "</tr>\n"
1744 		    	}
1745 				html = html
1746 					+ "<tr class='" + this.prefix + "menuitem'>"
1747 						+ "<td><a id='" + this.prefix + "clear' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/empty.png' title='" + SDK.translate("Clear the local chat log") + "'> " + SDK.translate("Clear log") + "</a></td>"
1748 					+ "</tr>\n"
1749 					+ "<tr class='" + this.prefix + "menuitem'>"
1750 						+ "<td><a id='" + this.prefix + "accept' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/accept.png' title='" + SDK.translate("Accept a private request from an operator, bot, or another user") + "'> " + SDK.translate("Accept private") + "</a></td>"
1751 					+ "</tr>\n";
1752 					if (this.showSendImage) {
1753 						html = html
1754 						+ "<tr class='" + this.prefix + "menuitem'>"
1755 							+ "<td><a id='" + this.prefix + "sendImage' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/image.png' title='" + SDK.translate("Resize and send an image attachment") + "'> " + SDK.translate("Send image") + "</a></td>"
1756 						+ "</tr>\n"
1757 						+ "<tr class='" + this.prefix + "menuitem'>"
1758 							+ "<td><a id='" + this.prefix + "sendAttachment' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/attach.png' title='" + SDK.translate("Send an image or file attachment") + "'> " + SDK.translate("Send file") + "</a></td>"
1759 						+ "</tr>\n";
1760 					}
1761 				if (this.emailChatLog) {
1762 					html = html
1763 						+ "<tr class='" + this.prefix + "menuitem'>"
1764 						+ "<td><a id='" + this.prefix + "emailChatLog' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img id='email' class='" + this.prefix + "menu' src='" + SDK.url + "/images/message.png' title='" + SDK.translate("Send yourself an email of the conversation log") + "'> " + SDK.translate("Email Chat Log") + "</a></td>"
1765 				}
1766 				html = html
1767 					+ "<tr class='" + this.prefix + "menuitem'>"
1768 						+ "<td><a id='" + this.prefix + "toggleChime' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img id='boxchime' class='" + this.prefix + "menu' src='" + SDK.url + "/images/sound.png' title='" + SDK.translate("Play a chime when a message is recieved") + "'> " + SDK.translate("Chime") + "</a></td>"
1769 					+ "</tr>\n"
1770 					+ "<tr class='" + this.prefix + "menuitem'>"
1771 						+ "<td><a id='" + this.prefix + "toggleSpeak' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img id='boxtalk' class='" + this.prefix + "menu' src='" + SDK.url + "/images/talkoff.png' title='" + SDK.translate("Speak each message using voice synthesis") + "'> " + SDK.translate("Text to speech") + "</a></td>"
1772 					+ "</tr>\n"
1773 					+ "<tr class='" + this.prefix + "menuitem'>"
1774 						+ "<td><a id='" + this.prefix + "toggleListen' class='" + this.prefix + "menuitem' onclick='return false;' href='#'>"
1775 								+ "<img id='boxmic' class='" + this.prefix + "menu' src='" + SDK.url + "/images/micoff.png' title='" + SDK.translate("Enable speech recognition (browser must support HTML5 speech recognition, such as Chrome)") + "'> " + SDK.translate("Speech recognition") + "</a>"
1776 						+ "</td>"
1777 					+ "</tr>\n"
1778 					+ "<tr class='" + this.prefix + "menuitem'>"
1779 						+ "<td><a id='" + this.prefix + "exit' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + SDK.url + "/images/quit.png' title='" + SDK.translate("Exit the channel or active private channel") + "'> " + SDK.translate("Quit private or channel") + "</a></td>"
1780 					+ "</tr>\n"
1781 					+ "</table>\n"
1782 					+ "</div>\n"
1783 					+ "<img class='" + this.prefix + "toolbar' src='" + SDK.url + "/images/menu.png'>\n"
1784 					+ "</span>\n"
1785 					+ "</div>\n"
1786 		    		+ "</span></td>\n";
1787 			}
1788 		html = html
1789 			+ "<td><span class='" + this.prefix + "box-input-span'><input id='" + this.prefix
1790 				+ "chat' type='text' id='" + this.prefix + "box-input' "
1791 				+ "class='" + this.prefix + "box-input'/></span></td>"
1792 			+ "</tr></table>"
1793 			+ "</div>\n"
1794 			+ "</div>\n"
1795 			+ "<div id='" + this.prefix + "boxbar' class='" + this.prefix + "boxbar'>"
1796 				+ "<div id='" + this.prefix + "boxbar2' class='" + this.prefix + "boxbar2'>"
1797 					+ "<span><a id='" + this.prefix + "boxbarmax' class='" + this.prefix + "boxbarmax' " + " onclick='return false;' href='#'><img id='" + this.prefix + "boxbarmaximage' " + "src='" + SDK.url + "/images/maximizew.png'> " + this.caption + " </a>";
1798 		if (this.showClose) {
1799 			html = html + " <a id='" + this.prefix + "boxclose' class='" + this.prefix + "boxclose' onclick='return false;' href='#'> <img src='" + SDK.url + "/images/closeg.png'></a>";
1800 		}
1801 		html = html
1802 				+ "</span><br>"
1803 			+ "</div>\n"
1804 			+ "<div id='" + this.prefix + "boxbar3' class='" + this.prefix + "boxbar3'" + ">"
1805 				+ "<span><a id='" + this.prefix + "boxbarmax2' class='" + this.prefix + "boxbarmax2' " + (this.forceStyles ? "style='color:white' " : "") + " onclick='return false;' href='#'>" + "</a>"
1806 				+ "</span><br>";
1807 		if (this.showClose) {
1808 			html = html + " <a id='" + this.prefix + "boxclose2' class='" + this.prefix + "boxclose2' onclick='return false;' href='#'> <img src='" + SDK.url + "/images/closeg.png'></a>";
1809 		}
1810 		html = html
1811 			+ "</div>\n"
1812 			+ "</span>"
1813 			+ "</div>\n";
1814 		
1815 		if (this.promptContactInfo) {
1816 			html = html
1817 				+ "<div id='" + this.prefix + "contactinfo' class='" + this.prefix + "box' " + backgroundstyle + ">"
1818 					+ "<div class='" + this.prefix + "boxmenu'>"
1819 						+ "<span style='float:right'><a id='" + this.prefix + "contactboxmin' class='" + this.prefix + "contactboxmin' onclick='return false;' href='#'><img src='" + SDK.url + "/images/minimize.png'></a>"
1820 					+ "</div>\n"
1821 					+ "<div style='margin:10px'>\n"
1822 						+ "<span>" + SDK.translate("Name") + "</span><br/><input id='" + this.prefix + "contactname' type='text' /><br/>\n"
1823 						+ "<span>" + SDK.translate("Email") + "</span><br/><input id='" + this.prefix + "contactemail' type='email' /><br/>\n"
1824 						+ "<span>" + SDK.translate("Phone") + "</span><br/><input id='" + this.prefix + "contactphone' type='text' /><br/>\n"
1825 						+ "<br/><a id='" + this.prefix + "contactconnect' class='" + this.prefix + "contactconnect' " + (this.forceStyles ? "style='color:white' " : "") + " onclick='return false;' href='#'>" + SDK.translate("Connect") + "</a>\n"
1826 					+ "</div>\n"
1827 				+ "</div>";
1828 		}
1829 		if (this.promptEmailChatLog) {
1830 			html = html
1831 				+ "<div id='" + this.prefix + "emailchatlogdialog' class='" + this.prefix + "box' " + backgroundstyle + ">"
1832 					+ "<div class='" + this.prefix + "boxmenu'>"
1833 						+ "<span style='float:right'><a id='" + this.prefix + "emailchatlogdialogmin' class='" + this.prefix + "boxmin' onclick='return false;' href='#'><img src='" + SDK.url + "/images/minimize.png'></a>"
1834 					+ "</div>\n"
1835 					+ "<div style='margin:10px;margin-bottom:20px;margin-top:20px;'>\n"
1836 						+ "<span>" + SDK.translate("Would you like a copy of the chat log sent to your email?") + "</span><br/><input id='" + this.prefix + "emailchatlogemail' type='email' /><br/>\n"
1837 						+ "<br/><a id='" + this.prefix + "emailchatlogdialogyes' class='" + this.prefix + "emailconfirm' " + (this.forceStyles ? "style='color:white' " : "") + " onclick='return false;' href='#'>" + SDK.translate("Yes") + "</a>\n"
1838 						+ " <a id='" + this.prefix + "emailchatlogdialogno' class='" + this.prefix + "emailconfirm' " + (this.forceStyles ? "style='color:white' " : "") + " onclick='return false;' href='#'>" + SDK.translate("No") + "</a>\n"
1839 					+ "</div>\n"
1840 				+ "</div>";
1841 		}
1842 		
1843 		box.innerHTML = html;
1844 		document.body.appendChild(box);
1845 		
1846 		if (this.online && this.chatroom) {
1847 			document.getElementById(this.prefix + "online").style.height = "95px";
1848 		}
1849 		if (this.chatLog && !this.bubble) {
1850 			var bubbleDiv = document.getElementById(this.prefix + 'bubble-div');
1851 			if (bubbleDiv != null) {
1852 				bubbleDiv.style.display = "none";
1853 			}
1854 			document.getElementById(this.prefix + 'no-bubble-plain').style.display = "none";
1855 			document.getElementById(this.prefix + 'response').style.display = "none";
1856 		}
1857 		
1858 		var self = this;
1859 		var listen = false;
1860 		if (document.getElementById(this.prefix + "chat") != null) {
1861 			document.getElementById(this.prefix + "chat").addEventListener("keypress", function(event) {
1862 				if (event.keyCode == 13) {
1863 					self.sendMessage();
1864 					return false;
1865 				}
1866 			});
1867 		}
1868 		if (document.getElementById(this.prefix + "exit") != null) {
1869 			document.getElementById(this.prefix + "exit").addEventListener("click", function() {
1870 				self.exit();
1871 				return false;
1872 			});
1873 		}
1874 		if (document.getElementById(this.prefix + "ping")!= null) {
1875 			document.getElementById(this.prefix + "ping").addEventListener("click", function() {
1876 				self.ping();
1877 				return false;
1878 			});
1879 		}
1880 		if (document.getElementById(this.prefix + "clear") != null) {
1881 			document.getElementById(this.prefix + "clear").addEventListener("click", function() {
1882 				self.clear();
1883 				return false;
1884 			});
1885 		}
1886 		if (document.getElementById(this.prefix + "accept") != null) {
1887 			document.getElementById(this.prefix + "accept").addEventListener("click", function() {
1888 				self.accept();
1889 				return false;
1890 			});
1891 		}
1892 		if (document.getElementById(this.prefix + "sendImage") != null) {
1893 			document.getElementById(this.prefix + "sendImage").addEventListener("click", function() {
1894 				self.sendImage();
1895 				return false;
1896 			});
1897 		}
1898 		if (document.getElementById(this.prefix + "sendAttachment") != null) {
1899 			document.getElementById(this.prefix + "sendAttachment").addEventListener("click", function() {
1900 				self.sendAttachment();
1901 				return false;
1902 			});
1903 		}
1904 		if (this.emailChatLog) {
1905 			if (document.getElementById(this.prefix + "emailChatLog") != null) {
1906 				document.getElementById(this.prefix + "emailChatLog").addEventListener("click", function() {
1907 					self.emailChatLog();
1908 					return false;
1909 				});
1910 			}
1911 		}
1912 		if (this.promptEmailChatLog) {
1913 			document.getElementById(this.prefix + "emailchatlogdialogyes").addEventListener("click", function() {
1914 				self.sendEmailChatLogBox();
1915 				return false;
1916 			});
1917 			document.getElementById(this.prefix + "emailchatlogdialogno").addEventListener("click", function() {
1918 				self.minimizeEmailChatLogBox();
1919 				return false;
1920 			});
1921 			document.getElementById(this.prefix + "emailchatlogdialogmin").addEventListener("click", function() {
1922 				self.minimizeEmailChatLogBox();
1923 				return false;
1924 			});
1925 		}
1926 		var menu = document.getElementById(this.prefix + "flag");
1927 		if (menu != null) {
1928 			menu.addEventListener("click", function() {
1929 				self.flag();
1930 				return false;
1931 			});
1932 		}
1933 		menu = document.getElementById(this.prefix + "whisper");
1934 		if (menu != null) {
1935 			menu.addEventListener("click", function() {
1936 				self.whisper();
1937 				return false;
1938 			});
1939 		}
1940 		menu = document.getElementById(this.prefix + "pvt");
1941 		if (menu != null) {
1942 			menu.addEventListener("click", function() {
1943 				self.pvt();
1944 				return false;
1945 			});
1946 		}
1947 		if (document.getElementById(this.prefix + "toggleChime") != null) {
1948 			document.getElementById(this.prefix + "toggleChime").addEventListener("click", function() {
1949 				self.toggleChime();
1950 				if (self.playChime) {
1951 					document.getElementById('boxchime').src = SDK.url + "/images/sound.png";
1952 				} else {
1953 					document.getElementById('boxchime').src = SDK.url + "/images/mute.png";
1954 				}
1955 			});
1956 		}
1957 		if (document.getElementById(this.prefix + "toggleSpeak") != null) {
1958 			document.getElementById(this.prefix + "toggleSpeak").addEventListener("click", function() {
1959 				self.toggleSpeak();
1960 				if (self.speak) {
1961 					document.getElementById('boxtalk').src = SDK.url + "/images/talk.png";
1962 				} else {
1963 					document.getElementById('boxtalk').src = SDK.url + "/images/talkoff.png";
1964 				}
1965 				return false;
1966 			});
1967 		}
1968 		if (document.getElementById(this.prefix + "toggleListen") != null) {
1969 			document.getElementById(this.prefix + "toggleListen").addEventListener("click", function() {
1970 				listen = !listen;
1971 				if (listen) {
1972 					SDK.startSpeechRecognition();
1973 					document.getElementById('boxmic').src = SDK.url + "/images/mic.png";
1974 				} else {
1975 					SDK.stopSpeechRecognition();
1976 					document.getElementById('boxmic').src = SDK.url + "/images/micoff.png";
1977 				}
1978 				return false;
1979 			});
1980 		}
1981 		document.getElementById(this.prefix + "boxmin").addEventListener("click", function() {
1982 			self.minimizeBox();
1983 			return false;
1984 		});
1985 		if (this.promptContactInfo) {
1986 			document.getElementById(this.prefix + "contactboxmin").addEventListener("click", function() {
1987 				self.minimizeContactInfoBox();
1988 				return false;
1989 			});
1990 			document.getElementById(this.prefix + "contactconnect").addEventListener("click", function() {
1991 				self.contactConnect();
1992 				return false;
1993 			});
1994 		}
1995 		if (document.getElementById(this.prefix + "boxmax") != null) {
1996 			document.getElementById(this.prefix + "boxmax").addEventListener("click", function() {
1997 				self.popup();
1998 				return false;
1999 			});
2000 		}
2001 		if (this.showClose) {
2002 			document.getElementById(this.prefix + "boxclose").addEventListener("click", function() {
2003 				self.closeBox();
2004 				return false;
2005 			});
2006 			document.getElementById(this.prefix + "boxclose2").addEventListener("click", function() {
2007 				self.closeBox();
2008 				return false;
2009 			});
2010 			document.getElementById(this.prefix + "boxbarmax").addEventListener("click", function() {
2011 				self.maximizeBox();
2012 				return false;
2013 			});
2014 			document.getElementById(this.prefix + "boxbarmax2").addEventListener("click", function() {
2015 				self.maximizeBox();
2016 				return false;
2017 			});
2018 		} else {
2019 			document.getElementById(this.prefix + "boxbar").addEventListener("click", function() {
2020 				self.maximizeBox();
2021 				return false;
2022 			});
2023 		}
2024 	}
2025 	
2026 	/**
2027 	 * Minimize the live chat embedding box.
2028 	 */
2029 	this.minimizeBox = function() {
2030 		this.onlineBar = false;
2031 		if (this.promptContactInfo) {
2032 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
2033 		}
2034 		document.getElementById(this.prefix + "box").style.display = 'none';
2035 		var onlineDiv = document.getElementById(this.prefix + "online");
2036 		if (onlineDiv != null) {
2037 			onlineDiv.style.display = 'none';
2038 		}
2039 		if (this.promptEmailChatLog) {
2040 			document.getElementById(this.prefix + "emailchatlogemail").value = this.contactEmail;
2041 			document.getElementById(this.prefix + "emailchatlogdialog").style.display = 'inline';
2042 			return false;
2043 		}
2044 		document.getElementById(this.prefix + "boxbar").style.display = 'inline';
2045 		if (this.prefix.indexOf("livechat") != -1) {
2046 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2047 			if (chatbot != null) {
2048 				chatbot.style.display = 'inline';
2049 			}
2050 		}
2051 		if (this.prefix.indexOf("chat") != -1) {
2052 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2053 			if (chatbot != null) {
2054 				chatbot.style.display = 'inline';
2055 			}
2056 		}
2057 		this.exit();
2058 		setTimeout(function() {
2059 		    self.exit();
2060 		}, 100);
2061 		return false;
2062 	}
2063 	
2064 	/**
2065 	 * Minimize the email chat log confirm dialog.
2066 	 */
2067 	this.minimizeEmailChatLogBox = function() {
2068 		if (this.promptContactInfo) {
2069 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
2070 		}
2071 		document.getElementById(this.prefix + "box").style.display = 'none';
2072 		document.getElementById(this.prefix + "emailchatlogdialog").style.display = 'none';
2073 		document.getElementById(this.prefix + "boxbar").style.display = 'inline';
2074 		if (this.prefix.indexOf("livechat") != -1) {
2075 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2076 			if (chatbot != null) {
2077 				chatbot.style.display = 'inline';
2078 			}
2079 		}
2080 		if (this.prefix.indexOf("chat") != -1) {
2081 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2082 			if (chatbot != null) {
2083 				chatbot.style.display = 'inline';
2084 			}
2085 		}
2086 		this.exit();
2087 		setTimeout(function() {
2088 		    self.exit();
2089 		}, 100);
2090 		return false;
2091 	}
2092 	
2093 	/**
2094 	 * Minimize the email chat log confirm dialog.
2095 	 */
2096 	this.sendEmailChatLogBox = function() {
2097 		this.contactEmail = document.getElementById(this.prefix + "emailchatlogemail").value;
2098 		this.sendEmailChatLog();
2099 		if (this.promptContactInfo) {
2100 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
2101 		}
2102 		document.getElementById(this.prefix + "box").style.display = 'none';
2103 		document.getElementById(this.prefix + "emailchatlogdialog").style.display = 'none';
2104 		document.getElementById(this.prefix + "boxbar").style.display = 'inline';
2105 		if (this.prefix.indexOf("livechat") != -1) {
2106 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2107 			if (chatbot != null) {
2108 				chatbot.style.display = 'inline';
2109 			}
2110 		}
2111 		if (this.prefix.indexOf("chat") != -1) {
2112 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2113 			if (chatbot != null) {
2114 				chatbot.style.display = 'inline';
2115 			}
2116 		}
2117 		setTimeout(function() {
2118 		    self.exit();
2119 		}, 100);
2120 		return false;
2121 	}
2122 	
2123 	/**
2124 	 * Minimize the contact info box.
2125 	 */
2126 	this.minimizeContactInfoBox = function() {
2127 		if (this.promptContactInfo) {
2128 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
2129 		}
2130 		document.getElementById(this.prefix + "box").style.display = 'none';
2131 		document.getElementById(this.prefix + "boxbar").style.display = 'inline';
2132 		if (this.prefix.indexOf("livechat") != -1) {
2133 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2134 			if (chatbot != null) {
2135 				chatbot.style.display = 'inline';
2136 			}
2137 		}
2138 		if (this.prefix.indexOf("chat") != -1) {
2139 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2140 			if (chatbot != null) {
2141 				chatbot.style.display = 'inline';
2142 			}
2143 		}
2144 		return false;
2145 	}
2146 	
2147 	/**
2148 	 * Check contact info and connect.
2149 	 */
2150 	this.contactConnect = function() {
2151 		this.hasContactInfo = true;
2152 		this.contactName = document.getElementById(this.prefix + "contactname").value;
2153 		var ok = true;
2154 		if (this.contactName != null && this.contactName == "") {
2155 			ok = false;
2156 			document.getElementById(this.prefix + "contactname").style.borderColor = "red";
2157 			document.getElementById(this.prefix + "contactname").placeholder = SDK.translate("Enter name");
2158 		}
2159 		this.contactEmail = document.getElementById(this.prefix + "contactemail").value;
2160 		if (this.contactEmail != null && this.contactEmail.indexOf("@") == -1) {
2161 			ok = false;
2162 			document.getElementById(this.prefix + "contactemail").style.borderColor = "red";
2163 			document.getElementById(this.prefix + "contactemail").placeholder = SDK.translate("Enter valid email");
2164 		}
2165 		this.contactPhone = document.getElementById(this.prefix + "contactphone").value;
2166 		this.contactInfo = this.contactName + " " + this.contactEmail + " " + this.contactPhone;
2167 		if (ok) {
2168 			this.maximizeBox();
2169 		}
2170 	}
2171 	
2172 	/**
2173 	 * Maximize the embedding div in the current webpage.
2174 	 */
2175 	this.maximizeBox = function() {
2176 		this.onlineBar = true;
2177 		if (this.promptContactInfo && !this.hasContactInfo) {
2178 			document.getElementById(this.prefix + "contactinfo").style.display = 'inline';
2179 			document.getElementById(this.prefix + "boxbar").style.display = 'none';
2180 			document.getElementById(this.prefix + "box").style.display = 'none';
2181 			if (this.prefix.indexOf("livechat") != -1) {
2182 				var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2183 				if (chatbot != null) {
2184 					chatbot.style.display = 'none';
2185 				}
2186 			}
2187 			if (this.prefix.indexOf("chat") != -1) {
2188 				var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2189 				if (chatbot != null) {
2190 					chatbot.style.display = 'none';
2191 				}
2192 			}
2193 		} else {
2194 			if (this.promptContactInfo) {
2195 				document.getElementById(this.prefix + "contactinfo").style.display = 'none';
2196 			}
2197 			document.getElementById(this.prefix + "boxbar").style.display = 'none';
2198 			document.getElementById(this.prefix + "box").style.display = 'inline';
2199 			if (this.prefix.indexOf("livechat") != -1) {
2200 				var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2201 				if (chatbot != null) {
2202 					chatbot.style.display = 'none';
2203 				}
2204 			}
2205 			if (this.prefix.indexOf("chat") != -1) {
2206 				var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2207 				if (chatbot != null) {
2208 					chatbot.style.display = 'none';
2209 				}
2210 			}
2211 		    var chat = new LiveChatConnection();
2212 		    chat.sdk = this.sdk;
2213 		    if (this.contactInfo != null) {
2214 		    	chat.contactInfo = this.contactInfo;
2215 		    }
2216 		    var channel = new ChannelConfig();
2217 		    channel.id = this.instance;
2218 		    chat.listener = this;
2219 			chat.connect(channel, this.sdk.user);
2220 			var self = this;
2221 			if (this.autoAccept != null) {
2222 				setTimeout(function() {
2223 					chat.accept();
2224 				}, self.autoAccept);
2225 			}
2226 		}
2227 		return false;
2228 	}
2229 	
2230 	/**
2231 	 * Close the embedding div in the current webpage.
2232 	 */
2233 	this.closeBox = function() {
2234 		if (this.promptContactInfo) {
2235 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
2236 		}
2237 		document.getElementById(this.prefix + "boxbar").style.display = 'none';
2238 		document.getElementById(this.prefix + "box").style.display = 'none';
2239 		if (this.prefix.indexOf("livechat") != -1) {
2240 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("livechat")) + "boxbar");
2241 			if (chatbot != null) {
2242 				chatbot.style.display = 'none';
2243 			}
2244 		}
2245 		if (this.prefix.indexOf("chat") != -1) {
2246 			var chatbot = document.getElementById(this.prefix.substring(0, this.prefix.indexOf("chat")) + "boxbar");
2247 			if (chatbot != null) {
2248 				chatbot.style.display = 'none';
2249 			}
2250 		}
2251 		this.exit();
2252 		var self = this;
2253 		setTimeout(function() {
2254 		    self.exit();
2255 		}, 100);
2256 		return false;		
2257 	}
2258 	
2259 	/**
2260 	 * Create a popup window live chat session.
2261 	 */
2262 	this.popup = function() {
2263 		var box = document.getElementById(this.prefix + "box");
2264 		if (box != null) {
2265 			box.style.display = 'none';
2266 		}
2267 		if (this.popupURL != null) {
2268 			var popupURL = this.popupURL;
2269 			if (popupURL.indexOf("livechat?") != -1 && this.contactInfo != null && this.contactInfo != "") {
2270 				popupURL = popupURL + "&info=" + encodeURI(this.contactInfo);
2271 			}
2272 			SDK.popupwindow(popupURL, 'child', 700, 520);
2273 		} else {
2274 		    var form = document.createElement("form");
2275             form.setAttribute("method", "post");
2276             form.setAttribute("action", SDK.url + "/livechat");
2277             form.setAttribute("target", 'child');
2278  
2279             var input = document.createElement('input');
2280             input.type = "hidden";
2281             input.name = "id";
2282             input.value = this.instance;
2283             form.appendChild(input);
2284  
2285             input = document.createElement('input');
2286             input.type = "hidden";
2287             input.name = "embedded";
2288             input.value = "embedded";
2289             form.appendChild(input);
2290  
2291             input = document.createElement('input');
2292             input.type = "hidden";
2293             input.name = "chat";
2294             input.value = "true";
2295             form.appendChild(input);
2296  
2297             input = document.createElement('input');
2298             input.type = "hidden";
2299             input.name = "application";
2300             input.value = this.connection.credentials.applicationId;
2301             form.appendChild(input);
2302  
2303             input = document.createElement('input');
2304             input.type = "hidden";
2305             input.name = "css";
2306             input.value = this.css;
2307             form.appendChild(input);
2308  
2309             input = document.createElement('input');
2310             input.type = "hidden";
2311             input.name = "info";
2312             input.value = this.contactInfo;
2313             form.appendChild(input);
2314             
2315             document.body.appendChild(form);
2316             
2317 			SDK.popupwindow('','child', 700, 520);
2318 			
2319 			form.submit();
2320             document.body.removeChild(form);
2321 		}
2322 		this.minimizeBox();
2323 		return false;
2324 	}
2325 	
2326 	window.onfocus = function() {
2327 		self.isActive = true;
2328 		if (document.title != self.windowTitle) {
2329 			document.title = self.windowTitle;
2330 		}
2331 	}; 
2332 
2333 	window.onblur = function() {
2334 		self.isActive = false;
2335 	};
2336 
2337 	
2338 	/**
2339 	 * Search for link using <a href="chat:yes">...
2340 	 * Switch them to use an onclick to post the chat back to the chat.
2341 	 */
2342 	this.linkChatPostbacks = function(node) {
2343 		var self = this;
2344         var links = node.getElementsByTagName("a");
2345         for (var index = 0; index < links.length; index++) {
2346         	var a = links[index];
2347         	var href = a.getAttribute("href");
2348         	if (href != null && href.indexOf("chat:") != -1) {
2349         		var chat = href.substring("chat:".length, href.length).trim();
2350         		var temp = function(param, element) {
2351         			element.onclick = function() {
2352         				self.connection.sendMessage(param);
2353 	        			return false;
2354 	        		};
2355         		}
2356         		temp(chat, a);
2357         	}
2358         }
2359         var buttons = node.getElementsByTagName("button");
2360         for (var index = 0; index < buttons.length; index++) {
2361         	var button = buttons[index];
2362         	if (button.parentNode.nodeName == "A") {
2363         		continue;
2364         	}
2365         	var chat = button.textContent;
2366         	if (chat != null && chat.length > 0) {
2367         		var temp = function(param, element) {
2368         			element.onclick = function() {
2369 	        			self.connection.sendMessage(param);
2370 	        			return false;
2371 	        		};
2372         		}
2373         		temp(chat, button);
2374         	}
2375         }
2376         var choices = node.getElementsByTagName("select");
2377         for (var index = 0; index < choices.length; index++) {
2378         	var choice = choices[index];
2379     		var temp = function(param) {
2380     			param.addEventListener("change", function() {
2381         			self.connection.sendMessage(param.value);
2382         			return false;
2383         		});
2384     		}
2385     		temp(choice);
2386         }
2387 	}
2388 		
2389 	/**
2390 	 * A user message was received from the channel.
2391 	 */
2392 	this.message = function(message) {
2393 		var index = message.indexOf(':');
2394 		var speaker = '';
2395 		if (index != -1) {
2396 			speaker = message.substring(0, index + 1);
2397 			responseText = message.substring(index + 2, message.length);
2398 		} else {
2399 			responseText = message;
2400 		}
2401 		if (speaker != (this.nick + ':')) {
2402 			if (this.playChime) {
2403 				SDK.chime();
2404 			}
2405 			if (this.speak) {
2406 				SDK.tts(SDK.stripTags(responseText), this.voice, this.nativeVoice, this.lang, this.nativeVoiceName);
2407 			}
2408 			document.getElementById(this.prefix + 'response').innerHTML = SDK.linkURLs(responseText);
2409 			this.linkChatPostbacks(document.getElementById(this.prefix + 'response'));
2410 			// Fix Chrome bug,
2411 			if (SDK.fixChromeResizeCSS && SDK.isChrome()) {
2412 				var padding = document.getElementById(this.prefix + 'response').parentNode.parentNode.style.padding;
2413 		    	document.getElementById(this.prefix + 'response').parentNode.parentNode.style.padding = "7px";
2414 		    	var self = this;
2415 				setTimeout(function() {
2416 		    		document.getElementById(self.prefix + 'response').parentNode.parentNode.style.padding = padding;
2417 				}, 10);
2418 			}
2419 		    this.switchText = false;
2420 		} else {
2421 		    this.switchText = true;
2422 		}
2423 		var scroller = document.getElementById(this.prefix + 'scroller');
2424 		var consolepane = document.getElementById(this.prefix + 'console');
2425 		if (scroller == null || consolepane == null) {
2426 			return;
2427 		}
2428 		if (this.chatLog) {
2429 			var tr = document.createElement('tr');
2430 			tr.style.verticalAlign = "top";
2431 			var td = document.createElement('td');
2432 			var td2 = document.createElement('td');
2433 			var div = document.createElement('div');
2434 			var span = document.createElement('span');
2435 			var br = document.createElement('br');
2436 			var span2 = document.createElement('span');
2437 			var div2 = document.createElement('div');
2438 			var chatClass = this.prefix + 'chat-1';
2439 			div.className = this.prefix + 'chat-1-div';
2440 			div2.className = this.prefix + 'chat-1-div-2';
2441 			span.className = this.prefix + 'chat-user-1';
2442 			td.className = this.prefix + 'chat-user-1';
2443 			if (this.switchText) {
2444 				td.className = this.prefix + 'chat-user-2';
2445 				chatClass = this.prefix + 'chat-2';
2446 			    div.className = this.prefix + 'chat-2-div';
2447 			    div2.className = this.prefix + 'chat-2-div-2';
2448 			    span.className = this.prefix + 'chat-user-2';
2449 			}
2450 			var userImg = document.createElement('img');
2451 			userImg.className = this.prefix + 'chat-user';
2452 			var speakerName = speaker.slice(0, -1);
2453 			if (speakerName != "Info" && speakerName != "Error") {
2454 				for(var key in this.users) {
2455 					if (key === speakerName) {
2456 						userImg.setAttribute('alt', speakerName);
2457 						userImg.setAttribute('src', this.users[key]);
2458 						break;
2459 					}
2460 				}
2461 			}
2462 			td.appendChild(userImg);
2463 			td.setAttribute('nowrap', 'nowrap');
2464 			td2.className = chatClass;
2465 			td2.setAttribute('align', 'left');
2466 			td2.setAttribute('width', '100%');
2467 			
2468 			var date = new Date(); 
2469 			var time = date.getHours() + ":" + ((date.getMinutes() < 10)? "0" : "") + date.getMinutes() + ":" + ((date.getSeconds() < 10)? "0" : "") + date.getSeconds();
2470 			span.innerHTML = speaker + " <small>" + time + "</small>";
2471 			span2.className = chatClass;
2472 			span2.innerHTML = SDK.linkURLs(responseText);
2473 			this.linkChatPostbacks(span2);
2474 			consolepane.appendChild(tr);
2475 			
2476 			tr.appendChild(td);
2477 			tr.appendChild(td2);
2478 			div.appendChild(span);
2479 			div.appendChild(br);
2480 			div.appendChild(div2);
2481 			td2.appendChild(div);
2482 			div2.appendChild(span2);
2483 		}
2484 		this.switchText = !this.switchText;
2485 		while (consolepane.childNodes.length > 500) {
2486 			consolepane.removeChild(consolepane.firstChild);
2487 		}
2488 		scroller.scrollTop = scroller.scrollHeight;
2489 		if (this.focus) {
2490 			document.getElementById(this.prefix + 'chat').focus();
2491 		}
2492 		if (!this.isActive) {
2493 			document.title = SDK.stripTags(responseText);
2494 		}
2495 	};
2496 	
2497 	/**
2498 	 * An informational message was received from the channel.
2499 	 * Such as a new user joined, private request, etc.
2500 	 */	
2501 	this.info = function(message) {
2502 		if (this.connection.nick != null && this.connection.nick != "") {
2503 			this.nick = this.connection.nick;
2504 		}
2505 		this.message(message);
2506 	};
2507 
2508 	/**
2509 	 * An error message was received from the channel.
2510 	 * This could be an access error, or message failure.
2511 	 */	
2512 	this.error = function(message) {
2513 		this.message(message);
2514 	};
2515 	
2516 	/**
2517 	 * Notification that the connection was closed.
2518 	 */
2519 	this.closed = function() {};
2520 	
2521 	/**
2522 	 * The channels users changed (user joined, left, etc.)
2523 	 * This contains a comma separated values (CSV) list of the current channel users.
2524 	 * It can be passed to the SDKConnection.getUsers() API to obtain the UserConfig info for the users.
2525 	 */
2526 	this.updateUsers = function(usersCSV) {};
2527 
2528 	/**
2529 	 * The channels users changed (user joined, left, etc.)
2530 	 * This contains a HTML list of the current channel users.
2531 	 * It can be inserted into an HTML document to display the users.
2532 	 */
2533 	this.updateUsersXML = function(usersXML) {
2534 		if (!this.linkUsers) {
2535 			usersXML = usersXML.split('<a').join('<span');
2536 			usersXML = usersXML.split('</a>').join('</span>');
2537 		}
2538 		var onlineList = document.getElementById(this.prefix + 'online');
2539 		if (onlineList == null) {
2540 			return;
2541 		}
2542 		if (!this.chatLog) {
2543 			onlineList.style.height = "60px";
2544 		}
2545 		var div = document.createElement('div');
2546 		div.innerHTML = usersXML;
2547 		var children = div.childNodes[0].childNodes;
2548 		var usersArray = {};
2549 		var size = children.length;
2550 		for(var i = 0; i < size; i++) {
2551 			var userName = children[i].innerText;
2552 			var child = children[i].childNodes;
2553 			var imageSrc = child[0].getAttribute('src');
2554 			usersArray[userName] = imageSrc;
2555 		}
2556 		this.users = usersArray;
2557 		if (this.onlineBar) {
2558 			var onlineBar = onlineList;
2559 			onlineBar.innerHTML = '';
2560 			if (this.chatroom || this.isFrame) { // displaying list of users on top
2561 				var count = 0;
2562 				var ids = {};
2563 				var length = children.length;
2564 				for (var i = 0; i < length; i++) {
2565 					var child = children[i - count];
2566 					ids[child.id] = child.id;
2567 					if (document.getElementById(child.id) == null) {
2568 						onlineList.appendChild(child);
2569 						count++;
2570 					}
2571 				}
2572 				onlineList.style.margin = "0";
2573 				onlineList.style.display = 'inline';
2574 			}
2575 			else { // displaying only single bot on top
2576 				var length = children.length;
2577 				var child = children[length - 1];
2578 				var keys = [];
2579 				for(var keyItem in this.users) {
2580 					keys.push(keyItem);
2581 				}
2582 				var botName = keys[keys.length - 1];
2583 				var botImageSrc = this.users[botName];
2584 				if (typeof botName === 'undefined' || typeof botImageSrc === 'undefined') {
2585 					return;
2586 				}
2587 				var botImage = document.createElement('img');
2588 				botImage.className = this.prefix + "-bot-image";
2589 				botImage.setAttribute('alt', botName);
2590 				botImage.setAttribute('src', botImageSrc);
2591 				var botSpan = document.createElement('span');
2592 				botSpan.className = this.prefix + "user-bot";
2593 				botSpan.innerHTML = botName;
2594 				onlineBar.append(botImage);
2595 				onlineBar.append(botSpan);
2596 				if (!this.isFrame) {
2597 					onlineList.style.display = 'block';
2598 					var line = document.createElement('hr');
2599 					var onlineDiv = document.getElementById(this.prefix + 'online-div');
2600 					onlineDiv.appendChild(line);
2601 				}
2602 			}
2603 			return;
2604 		}
2605 		onlineList.innerHTML = '';
2606 		var div = document.createElement('div');
2607 		div.innerHTML = usersXML;
2608 		var children = div.childNodes[0].childNodes;
2609 		var count = 0;
2610 		var length = children.length;
2611 		var ids = {};
2612 		// Add missing user
2613 		for (var i = 0; i < length; i++) {
2614 			var child = children[i - count];
2615 			ids[child.id] = child.id;
2616 			if (document.getElementById(child.id) == null) {
2617 				onlineList.appendChild(child);
2618 				count++;
2619 			}
2620 		}
2621 		// Remove missing users
2622 		var onlineDiv = document.getElementById(this.prefix + 'online-div');
2623 		if (onlineDiv == null) {
2624 			return;
2625 		}
2626 		children = onlineDiv.childNodes;
2627 		count = 0;
2628 		length = children.length;
2629 		for (var i = 0; i < length; i++) {
2630 			var child = children[i - count];
2631 			if (child.id != (this.prefix + 'online') && ids[child.id] == null) {
2632 				onlineDiv.removeChild(child);
2633 				count++;
2634 			}
2635 		}
2636 	};
2637 
2638 	/**
2639 	 * Decrease the size of the video element for the userid.
2640 	 */
2641 	this.shrinkVideo = function(user) {
2642 	    var id = 'user-' + encodeURIComponent(user);
2643 	    var userdiv = document.getElementById(id);
2644 	    if (userdiv != null) {
2645 	    	var media = userdiv.firstElementChild;	    	
2646 			if (media != null) {
2647 				media.height = media.height / 1.5;
2648 			}
2649 		}
2650 	};
2651 
2652 	/**
2653 	 * Increase the size of the video element for the userid.
2654 	 */
2655 	this.expandVideo = function(user) {
2656 	    var id = 'user-' + encodeURIComponent(user);
2657 	    var userdiv = document.getElementById(id);
2658 	    if (userdiv != null) {
2659 	    	var media = userdiv.firstElementChild;	    	
2660 			if (media != null) {
2661 				media.height = media.height * 1.5;
2662 			}
2663 		}
2664 	};
2665 
2666 	/**
2667 	 * Mute the audio for the userid.
2668 	 */
2669 	this.muteAudio = function(user) {
2670 	    var id = 'user-' + encodeURIComponent(user);
2671 	    var userdiv = document.getElementById(id);
2672 	    if (userdiv != null) {
2673 	    	var media = userdiv.firstElementChild;	    	
2674 			if (media != null) {
2675 				if (media.muted) {
2676 					if (user != this.nick) {
2677 						media.muted = false;
2678 					}
2679 				} else {
2680 					media.muted = true;					
2681 				}
2682 			}
2683 		}
2684 	};
2685 
2686 	/**
2687 	 * Mute the video for the userid.
2688 	 */
2689 	this.muteVideo = function(user) {
2690 	    var id = 'user-' + encodeURIComponent(user);
2691 	    var userdiv = document.getElementById(id);
2692 	    if (userdiv != null) {
2693 	    	var media = userdiv.firstElementChild;	    	
2694 			if (media != null) {
2695 				if (media.paused) {
2696 					media.play();
2697 					media.style.opacity = 100;
2698 				} else {
2699 					media.pause();
2700 					media.style.opacity = 0;					
2701 				}
2702 			}
2703 		}
2704 	};
2705 
2706 	this.toggleChime = function() {
2707 		this.playChime = !this.playChime;
2708 	}
2709 
2710 	this.toggleSpeak = function() {
2711 		this.speak = !this.speak;
2712 	}
2713 
2714 	this.toggleKeepAlive = function() {
2715 		this.connection.toggleKeepAlive();
2716 	}
2717 	
2718 	this.sendMessage = function() {
2719 		var message = document.getElementById(this.prefix + 'chat').value;
2720 		if (message != '') {
2721 			this.connection.sendMessage(message);
2722 			document.getElementById(this.prefix + 'chat').value = '';
2723 		}
2724 		return false;
2725 	};
2726 
2727 	this.sendImage = function() {
2728 		if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
2729 			alert('The File APIs are not fully supported in this browser.');
2730 			return false;
2731 		}
2732 		var form = document.createElement("form");
2733 		form.enctype = "multipart/form-data";
2734 		form.method = "post";
2735 		form.name = "fileinfo";
2736 		var fileInput = document.createElement("input");
2737 		var self = this;
2738 		fileInput.name = "file";
2739 		fileInput.type = "file";
2740 		form.appendChild(fileInput);
2741 		fileInput.onchange = function() {
2742 			var file = fileInput.files[0];
2743 			self.connection.sendAttachment(file, true, form);
2744 		}
2745 		fileInput.click();
2746 		return false;
2747 	};
2748 
2749 	this.sendAttachment = function() {
2750 		if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
2751 			alert('The File APIs are not fully supported in this browser.');
2752 			return false;
2753 		}
2754 		var form = document.createElement("form");
2755 		form.enctype = "multipart/form-data";
2756 		form.method = "post";
2757 		form.name = "fileinfo";
2758 		var fileInput = document.createElement("input");
2759 		var self = this;
2760 		fileInput.name = "file";
2761 		fileInput.type = "file";
2762 		form.appendChild(fileInput);
2763 		fileInput.onchange = function() {
2764 			var file = fileInput.files[0];
2765 			self.connection.sendAttachment(file, false, form);
2766 		}
2767 		fileInput.click();
2768 		return false;
2769 	};
2770 
2771 	this.ping = function() {
2772 		this.connection.ping();
2773 		document.getElementById(this.prefix + 'chat').value = '';
2774 		return false;
2775 	};
2776 
2777 	this.accept = function() {
2778 		this.connection.accept();
2779 		document.getElementById(this.prefix + 'chat').value = '';
2780 		return false;
2781 	};
2782 
2783 	this.exit = function() {
2784 		if (this.connection != null) {
2785 			this.connection.exit();
2786 			document.getElementById(this.prefix + 'chat').value = '';
2787 		}
2788 		return false;
2789 	};
2790 
2791 	this.spyMode = function() {
2792 		this.connection.spyMode();
2793 		document.getElementById(this.prefix + 'chat').value = '';
2794 		return false;
2795 	};
2796 
2797 	this.normalMode = function() {
2798 		this.connection.normalMode();
2799 		document.getElementById(this.prefix + 'chat').value = '';
2800 		return false;
2801 	};
2802 
2803 	this.boot = function() {
2804 		document.getElementById(this.prefix + 'chat').value = 'boot: user';
2805 		return false;
2806 	};
2807 
2808 	this.emailChatLog = function() {
2809 		document.getElementById(this.prefix + 'chat').value = 'email: ' + (this.contactEmail == null ? '[email protected]' : this.contactEmail);
2810 		return false;
2811 	};
2812 
2813 	this.sendEmailChatLog = function() {
2814 		this.connection.sendMessage('email: ' + this.contactEmail);
2815 		return false;
2816 	};
2817 
2818 	this.whisper = function(user) {
2819 		if (user == null) {
2820 			user = 'user';
2821 		}
2822 		document.getElementById(this.prefix + 'chat').value = 'whisper: ' + user + ': message';
2823 		return false;
2824 	};
2825 
2826 	this.flag = function(user) {
2827 		if (user != null) {
2828 			document.getElementById(this.prefix + 'chat').value = 'flag: ' + user + ': reason';
2829 			return false;
2830 		}
2831 		document.getElementById(this.prefix + 'chat').value = 'flag: user: reason';
2832 		return false;
2833 	};
2834 
2835 	this.pvt = function(user) {
2836 		if (user != null) {
2837 			this.connection.pvt(user);
2838 			return false;
2839 		}
2840 		document.getElementById(this.prefix + 'chat').value = 'private: user';
2841 		return false;
2842 	};
2843 
2844 	this.clear = function() {
2845 		document.getElementById(this.prefix + 'response').innerHTML = '';
2846 		var console = document.getElementById(this.prefix + 'console');
2847 		if (console != null) {
2848 			console.innerHTML = '';
2849 		}
2850 		return false;
2851 	};
2852 }
2853 
2854 /**
2855  * Shared method for updating an avatar image/video/audio from the chat response.
2856  */
2857 SDK.updateAvatar = function(responseMessage, speak, urlprefix, elementPrefix, channelaudio, afterFunction, nativeVoice, lang, voice) {
2858 	try {
2859 
2860 	var noMedia = false;
2861 	if (SDK.canPlayVideo == null) {
2862 		if (!SDK.isMobile()) {
2863 			SDK.canPlayVideo = true;
2864 		} else {
2865 			SDK.canPlayVideo = false;
2866 			SDK.canPlayAudio = true;
2867 			// Check if auto play has been disable by the browser (mobile Chrome/Safari)
2868 			setTimeout(function() {
2869 				if (noMedia) {
2870 					SDK.canPlayVideo = null;
2871 					SDK.canPlayAudio = null;
2872 				} else {
2873 					if (SDK.canPlayVideo == false) {
2874 						SDK.initVideo = function() {
2875 							SDK.canPlayVideo = true;
2876 							SDK.audio = new Audio(SDK.url + '/chime.mp3');
2877 							SDK.audio.load();
2878 							SDK.autoPlayActionAudio = new Audio(SDK.url + '/chime.mp3');
2879 							SDK.autoPlayActionAudio.load();
2880 							SDK.autoPlayBackgroundAudio = new Audio(SDK.url + '/chime.mp3');
2881 							SDK.autoPlayBackgroundAudio.load();
2882 							SDK.updateAvatar(responseMessage, speak, urlprefix, elementPrefix, channelaudio, afterFunction, nativeVoice, lang, voice);
2883 							document.getElementById("sdkplaybutton2").style.display = "none";
2884 						};
2885 						var body = document.body || document.getElementsByTagName('body')[0];
2886 						var playButton = document.createElement('div');
2887 						var html = "<div id='sdkplaybutton2' style='position:fixed;bottom:32px;left:32px;z-index:164;'><img onclick='SDK.initVideo()' width='64' src='"
2888 							+ SDK.url + "/images/playsound.png'/></div>"
2889 						playButton.innerHTML = html;
2890 						body.appendChild(playButton);
2891 						setTimeout(function() {
2892 							document.getElementById("sdkplaybutton2").style.display = "none";
2893 						}, 10000);
2894 					}
2895 				}
2896 			}, SDK.autoPlayDelay);
2897 		}
2898 	}
2899 	nativeVoice = nativeVoice && SDK.speechSynthesis;
2900 	if (elementPrefix == null) {
2901 		elementPrefix = "";
2902 	}
2903 	var avatarStatus = document.getElementById(elementPrefix + "avatar-status");
2904 	if (avatarStatus != null) {
2905 		var status = "";
2906 		if (responseMessage.emote != null && responseMessage.emote != "" && responseMessage.emote != "NONE") {
2907 			status = responseMessage.emote.toLowerCase();
2908 		}
2909 		if (responseMessage.action != null && responseMessage.action != "") {
2910 			if (status != "") {
2911 				status = status + " : ";
2912 			}
2913 			status = status + responseMessage.action;
2914 		}
2915 		if (responseMessage.pose != null && responseMessage.pose != "") {
2916 			if (status != "") {
2917 				status = status + " : ";
2918 			}
2919 			status = status + responseMessage.pose;
2920 		}
2921 		avatarStatus.innerHTML = status;
2922 	}
2923 	if (responseMessage.avatarActionAudio != null && speak) {
2924 		var audio = SDK.autoPlayActionAudio;
2925 		if (audio == null) {
2926 			audio = new Audio(urlprefix + responseMessage.avatarActionAudio);
2927 		} else {
2928 			audio.src = urlprefix + responseMessage.avatarActionAudio;
2929 		}
2930 		if (SDK.canPlayVideo == null) {
2931 			audio.onplaying = new function() {
2932 			    SDK.canPlayVideo = true;
2933 			}
2934 		}
2935 		audio.play();
2936 	}
2937 	if (!speak || SDK.currentBackgroundAudio != responseMessage.avatarAudio) {
2938 		// Only switch if different audio.
2939 		if (SDK.backgroundAudio != null) {
2940 			SDK.backgroundAudio.pause();
2941 			SDK.currentBackgroundAudio = null;
2942 		}
2943 		if (responseMessage.avatarAudio != null && speak) {
2944 			SDK.currentBackgroundAudio = responseMessage.avatarAudio;
2945 			SDK.backgroundAudio = SDK.autoPlayBackgroundAudio;
2946 			if (SDK.backgroundAudio == null) {
2947 				SDK.backgroundAudio = new Audio(urlprefix + responseMessage.avatarAudio);
2948 			} else {
2949 				SDK.backgroundAudio.src = urlprefix + responseMessage.avatarAudio;
2950 			}
2951 			SDK.backgroundAudio.loop = true;
2952 			if (SDK.canPlayVideo == null) {
2953 				SDK.backgroundAudio.onplaying = new function() {
2954 				    document.getElementById("native-voice-name").value = "onplaying-back";
2955 					SDK.canPlayVideo = true;
2956 				}
2957 			}
2958 			SDK.backgroundAudio.play();
2959 		}
2960 	}
2961 	var video = document.getElementById(elementPrefix + "avatar-video");
2962 	var isVideo = responseMessage.avatarType != null && responseMessage.avatarType.indexOf("video") != -1;
2963 	var useVideo = video != null && SDK.useVideo != false && (SDK.useVideo == true || !(SDK.isSafari() && SDK.isIPhone()));
2964 	if (isVideo && useVideo) {
2965 		var div = document.getElementById(elementPrefix + "avatar-image-div");
2966 		if (div != null) {
2967 			div.style.display = "none";
2968 		}
2969 		div = document.getElementById(elementPrefix + "avatar-game-div");
2970 		if (div != null) {
2971 			div.style.display = "none";
2972 		}
2973 		div = document.getElementById(elementPrefix + "avatar-video-div");
2974 		var canvas = null;
2975 		if (div != null) {
2976 			div.style.display = "inline-block";
2977 			if (responseMessage.avatarBackground != null) {
2978 				div.style.backgroundImage = "url(" + urlprefix + responseMessage.avatarBackground + ")";
2979 			}
2980 			var canvasDiv = document.getElementById(elementPrefix + "avatar-canvas-div");
2981 			if (((SDK.isChrome() && !SDK.isMobile()) || (SDK.isFirefox() && !SDK.isMac()) || SDK.useCanvas == true) && SDK.useCanvas != false && canvasDiv != null) {
2982 				div.style.position = "fixed";
2983 				div.style.top = "-1000";
2984 				div.style.left = "-1000";
2985 				div.style.opacity = "0";
2986 				div.style.zIndex = "-1";
2987 				canvasDiv.style.display = "inline-block";
2988 				canvas = document.getElementById(elementPrefix + "avatar-canvas");
2989 			}
2990 		}
2991 		if (SDK.canPlayVideo == null) {
2992 			video.onplaying = new function() {
2993 			    document.getElementById("native-voice-name").value = "onplaying-video";
2994 				SDK.canPlayVideo = true;
2995 			}
2996 		}
2997 		if (responseMessage.avatar.indexOf("mp4") != -1 && (SDK.isChrome() || SDK.isFirefox() || SDK.fixBrightness != true) && SDK.fixBrightness != false) {
2998 			// Hack to fix grey background in Chrome.
2999 			if (SDK.isChrome()) {
3000 				video.style.webkitFilter = "brightness(108.5%)";
3001 			} else {
3002 				video.style["filter"] = "brightness(1.085)";
3003 			}
3004 			if (canvas != null) {
3005 				if (SDK.isChrome()) {
3006 					canvas.style.webkitFilter = "brightness(108.5%)";
3007 				} else {
3008 					video.style["filter"] = "brightness(1.085)";
3009 				}
3010 			}
3011 		}
3012 		if (canvas == null) {
3013 			if (responseMessage.avatarBackground != null) {
3014 				video.poster = urlprefix + responseMessage.avatarBackground;				
3015 			}
3016 		}
3017 		var context = null;
3018 		var drawCanvas = null;
3019 		if (canvas != null) {
3020 		    context = canvas.getContext('2d');
3021 			if (SDK.timers[elementPrefix + "avatar-canvas"] == null) {
3022 				drawCanvas = function() {
3023 				    if (!video.paused && !video.ended && video.currentTime > 0) {
3024 				    	if (canvas.width != video.offsetWidth) {
3025 				    		canvas.width = video.offsetWidth;
3026 				    	}
3027 				    	if (canvas.height != video.offsetHeight) {
3028 				    		canvas.height = video.offsetHeight;
3029 				    	}
3030 				    	context.clearRect(0, 0, canvas.width, canvas.height);
3031 				    	context.drawImage(video, 0, 0, video.offsetWidth, video.offsetHeight);
3032 				    }
3033 				}
3034 				SDK.timers[elementPrefix + "avatar-canvas"] = drawCanvas;
3035 				setInterval(drawCanvas, 20);				
3036 			}
3037 		}
3038 		var end = function() {
3039 			video.src = urlprefix + responseMessage.avatar;
3040 			if (responseMessage.avatar2 == null) {
3041 				video.loop = true;
3042 			} else {
3043 				video.loop = false;
3044 				video.onended = function() {
3045 					var index = Math.floor(Math.random() * 5);
3046 					if (index == 4 && responseMessage.avatar5 != null) {
3047 						video.src = urlprefix + responseMessage.avatar5;
3048 					} else if (index == 3 && responseMessage.avatar4 != null) {
3049 						video.src = urlprefix + responseMessage.avatar4;
3050 					} else if (index == 2 && responseMessage.avatar3 != null) {
3051 						video.src = urlprefix + responseMessage.avatar3;
3052 					} else if (index == 1 && responseMessage.avatar2 != null) {
3053 						video.src = urlprefix + responseMessage.avatar2;
3054 					} else {
3055 						video.src = urlprefix + responseMessage.avatar;
3056 					}
3057 					video.play();
3058 				}
3059 			}
3060 			video.play();
3061 			if (afterFunction != null) {
3062 				afterFunction();
3063 			}
3064 		}
3065 		var talk = function() {
3066 			if (responseMessage.message != null && responseMessage.message.length > 0) {
3067 				if (responseMessage.avatarTalk != null) {
3068 					if (speak) {
3069 						if (responseMessage.speech == null && !nativeVoice) {
3070 							end();
3071 						} else {
3072 							video.src = urlprefix + responseMessage.avatar;
3073 							video.loop = true;
3074 							var playing = false;
3075 							video.play();
3076 	
3077 							if (nativeVoice) {
3078 								if ('SpeechSynthesisUtterance' in window) {
3079 									utterance = new SpeechSynthesisUtterance(SDK.stripTags(responseMessage.message));
3080 								} else {
3081 									utterance = new SpeechSynthesisUtterance2(SDK.stripTags(responseMessage.message));
3082 								}
3083 								SDK.utterance = utterance;
3084 								utterance.onstart = function() {
3085 									if (playing) {
3086 										return false;
3087 									}
3088 									if ('speechSynthesis' in window) {
3089 										speechSynthesis.pause();
3090 									}
3091 									video.src = urlprefix + responseMessage.avatarTalk;
3092 									video.loop = true;
3093 									video.oncanplay = function() {
3094 										if (playing) {
3095 											return false;
3096 										}
3097 										playing = true;
3098 										if ('speechSynthesis' in window) {
3099 											speechSynthesis.resume();
3100 										}
3101 									}
3102 									video.play();
3103 								}
3104 								utterance.onerror = function() {
3105 									console.log("error");
3106 									end();
3107 								}
3108 								utterance.onend = function() {
3109 									end();
3110 								}
3111 								
3112 								SDK.nativeTTS(utterance, lang, voice);
3113 							} else {
3114 								//var audio = new Audio(urlprefix + responseMessage.speech, channelaudio);
3115 								var audio = SDK.play(urlprefix + responseMessage.speech, channelaudio);
3116 								//audio.onabort = function() {console.log("abort");}
3117 								audio.oncanplay = function() {
3118 									if (playing) {
3119 										return false;
3120 									}
3121 									audio.pause();
3122 									video.src = urlprefix + responseMessage.avatarTalk;
3123 									video.loop = true;
3124 									video.oncanplay = function() {
3125 										if (playing) {
3126 											return false;
3127 										}
3128 										playing = true;
3129 										audio.play();
3130 									}
3131 									video.play();
3132 								}
3133 								audio.onerror = function() {
3134 									console.log("error");
3135 									end();
3136 								}
3137 								//audio.onloadeddata = function() {console.log("loadeddata");}
3138 								//audio.onloadedmetadata = function() {console.log("loadedmetadata");}
3139 								//audio.onpause = function() {console.log("pause");}
3140 								//audio.onplay = function() {console.log("play");}
3141 								//audio.onplaying = function() {console.log("playing");}
3142 								//audio.ontimeupdate = function() {console.log("timeupdate");}
3143 								var onended = audio.onended;
3144 								audio.onended = function() {
3145 									if (onended != null) {
3146 										onended();
3147 									}
3148 									end();
3149 								}
3150 								audio.play();
3151 								video.play();
3152 							}
3153 						}
3154 					} else {
3155 						video.src = urlprefix + responseMessage.avatarTalk;
3156 						video.loop = false;
3157 						video.play();
3158 						var onended = video.onended;
3159 						video.onended = function() {
3160 							if (onended != null) {
3161 								onended();
3162 							}
3163 							end();
3164 						}
3165 					}
3166 				} else {
3167 					video.src = urlprefix + responseMessage.avatar;
3168 					video.loop = true;
3169 					video.play();
3170 					if (speak) {
3171 						if (nativeVoice) {
3172 							if ('SpeechSynthesisUtterance' in window) {
3173 								utterance = new SpeechSynthesisUtterance(SDK.stripTags(responseMessage.message));
3174 							} else {
3175 								utterance = new SpeechSynthesisUtterance2(SDK.stripTags(responseMessage.message));
3176 							}
3177 							utterance.onend = afterFunction;
3178 							SDK.nativeTTS(utterance, lang, voice);
3179 						} else {
3180 							var audio = SDK.play(urlprefix + responseMessage.speech, channelaudio);
3181 							var onended = audio.onended;
3182 							audio.onended = function() {
3183 								if (onended != null) {
3184 									onended();
3185 								}
3186 								if (afterFunction != null) {
3187 									afterFunction();
3188 								}
3189 							}
3190 						}
3191 					} else if (afterFunction != null) {
3192 						afterFunction();			
3193 					}
3194 				}
3195 			} else {
3196 				end();
3197 			}
3198 		}
3199 		
3200 		if (responseMessage.avatarAction != null) {
3201 			video.src = urlprefix + responseMessage.avatarAction;
3202 			video.loop = false;
3203 			video.play();
3204 			var onended = video.onended;
3205 			video.onended = function() {
3206 				if (onended != null) {
3207 					onended();
3208 				}
3209 				talk();
3210 			}
3211 		} else {
3212 			talk();
3213 		}
3214 	} else {
3215 		var div = document.getElementById(elementPrefix + "avatar-video-div");
3216 		if (div != null) {
3217 			div.style.display = "none";
3218 		}
3219 		div = document.getElementById(elementPrefix + "avatar-canvas-div");
3220 		if (div != null) {
3221 			div.style.display = "none";
3222 		}
3223 		div = document.getElementById(elementPrefix + "avatar-game-div");
3224 		if (div != null) {
3225 			div.style.display = "none";
3226 		}
3227 		div = document.getElementById(elementPrefix + "avatar-image-div");
3228 		if (div != null) {
3229 			div.style.display = "inline-block";
3230 		}
3231 		var img = document.getElementById(elementPrefix + 'avatar');
3232 		if (img != null) {
3233 			if (isVideo) {
3234 				img.src = urlprefix + responseMessage.avatarBackground;
3235 			} else {
3236 				img.src = urlprefix + responseMessage.avatar;
3237 			}
3238 		}
3239 		img = document.getElementById(elementPrefix + 'avatar2');
3240 		if (img != null) {
3241 			if (isVideo) {
3242 				img.src = urlprefix + responseMessage.avatarBackground;
3243 			} else {
3244 				img.src = urlprefix + responseMessage.avatar;
3245 			}
3246 		}
3247 		if (speak && responseMessage.message != null && responseMessage.message.length > 0) {
3248 			if (nativeVoice) {
3249 				noMedia = true;
3250 				SDK.tts(SDK.stripTags(responseMessage.message), null, true, lang, voice);
3251 			} else if (responseMessage.speech != null) {
3252 				var audio = SDK.play(urlprefix + responseMessage.speech, channelaudio);
3253 				var onended = audio.onended;
3254 				audio.onended = function() {
3255 					if (onended != null) {
3256 						onended();
3257 					}
3258 					if (afterFunction != null) {
3259 						afterFunction();
3260 					}
3261 				}
3262 			}
3263 		} else {
3264 			noMedia = true;
3265 			if (afterFunction != null) {
3266 				afterFunction();
3267 			}
3268 		}
3269 	}
3270 	
3271 	} catch(err) {
3272 	}
3273 }
3274 
3275 /**
3276  * The WebChatbotListener provides an integration between a chat bot conversation through a SDKConnection and an HTML document.
3277  * It updates the document to messages received from the connection, and sends messages from the document's form.
3278  * The HTML document requires the following elements:
3279  * <ul>
3280  * <li> chat - input (type='text') element for sending messages
3281  * <li> send - input (type='submit') button for sending chat input
3282  * <li> response - p element paragraph for last chat message
3283  * <li> console - table element for chat log, and avatar
3284  * <li> scroller - div element for chat log scroll pane
3285  * <li> avatar - img element for the bot's avatar (optional)
3286  * <li> avatar2 - img element hover img for the bot's avatar (optional)
3287  * <li> avatar-image-div - div element for the bot's image (optional)
3288  * <li> avatar-video - video element for the bot's video (optional)
3289  * <li> avatar-video-div - div element for the bot's video (optional)
3290  * <li> avatar-status - span element for the bot's current status (optional)
3291  * </ul>
3292  * If a prefix is set, these id will be prefixed by the prefix.
3293  * Or you can call createBox() to have the WebChatbotListener create its own components in the current page.
3294  * @class
3295  */
3296 function WebChatbotListener() {
3297 	/** Set the caption for the button bar button. */
3298 	this.caption = null;
3299 	/** Disallow speech. */
3300 	this.allowSpeech = true;
3301 	/** Disallow image/file upload menus and buttons. */
3302 	this.allowFiles = true;
3303 	/** Add image/file upload buttons to toolbar. */
3304 	this.showFileButtons = false;
3305 	/** Remove menubar. */
3306 	this.showMenubar = true;
3307 	/** Show Box Max*/
3308 	this.showBoxmax = true;
3309 	/** Show Send Image*/
3310 	this.showSendImage = true;
3311 	/** Remove language choice. */
3312 	this.showChooseLanguage = true;
3313 	/** Enable or disable speech. */
3314 	this.speak = true;
3315 	/** Configure if the browser's native voice TTS should be used. */
3316 	this.nativeVoice = false;
3317 	/** Set the voice name for the native voice. */
3318 	this.nativeVoiceName = null;
3319 	/** Set the language for the native voice. */
3320 	this.lang = null;
3321 	/** Translate between the user's language, and the bot's language. */
3322 	this.translate = false;
3323 	/** Enable or disable avatar. */
3324 	this.avatar = true;
3325 	/** Set the avatar. */
3326 	this.avatarId = null;
3327 	/** Set if the avatar should request HD (high def) video/images. */
3328 	this.hd = null;
3329 	/** Set if the avatar should request a specific video or image format. */
3330 	this.format = null;
3331 	/** A SDK connection must be set, be sure to include your app id. */
3332 	this.connection = null;
3333 	/** The id or name of the bot instance to connect to. */
3334 	this.instance = null;
3335 	/** The name to display for the bot. */
3336 	this.instanceName = "Bot";
3337 	/** The name to display for the user. */
3338 	this.userName = "You";
3339 	/** Allow the button color to be set. */
3340 	this.color = "#009900";
3341 	/** Allow the different style sheet options */
3342 	this.version = null;
3343 	/** Set if the box chat log should be shown. */
3344 	this.chatLog = true;
3345 	/** Allow the hover button color to be set. */
3346 	this.hoverColor = "grey";
3347 	/** Allow the background color to be set. */
3348 	this.background = null;
3349 	/** Avatar image/video width. */
3350 	this.width = 300;
3351 	/** Avatar image/video height. */
3352 	this.height = null;
3353 	/** Chat bar offest from side. */
3354 	this.offset = 30;
3355 	/** Chat Button Vertial Offset*/
3356 	this.verticalOffset = 0;
3357 	/** Only apply the background color if not Chrome. */
3358 	this.backgroundIfNotChrome = false;
3359 	/** onresponse event is raised after a response is received. */
3360 	this.onresponse = null;
3361 	/** Configure if chat should be given focus after response. */
3362 	this.focus = true;
3363 	/** Override the URL used in the chat bot box popup. */
3364 	this.popupURL = null;
3365 	/** Print response in chat bubble. */
3366 	this.bubble = false;
3367 	/** Initial message to send to the bot. */
3368 	this.greetingMessage = null;
3369 	/** Initial message to display from the bot. (it is normally better to set a greeting in the bot instead). */
3370 	this.greeting = null;
3371 	/** Loading message to display. */
3372 	this.loading = "loading...";
3373 	/** Element id and class prefix. This allows an id and class prefix to avoid name collisions on the element names for the chat, response, console, and avatar elements.*/
3374 	this.prefix = "";
3375 	/** This can be used to keep the bot's chat bar in synch with a livechat bar. */
3376 	this.livechatPrefix = null;
3377 	/** Allows the bot's thumbnail image to be set for chat log. */
3378 	this.botThumb = {};
3379 	/** Allows the user's thumbnail image to be set for chat log. */
3380 	this.userThumb = {};
3381 	/** Set styles explictly to avoid inheriting page styles. Disable this to be able to override styles. */
3382 	this.forceStyles = true;
3383 	/** Add additional css style code. */
3384 	this.style = "";
3385 	/** Set the location of the button and box, one of "bottom-right", "bottom-left", "top-right", "top-left". */
3386 	this.boxLocation = "bottom-right";
3387 	/** Prompt for name/email before connecting. */
3388 	this.promptContactInfo = false;
3389 	this.hasContactInfo = false;
3390 	this.contactName = null;
3391 	this.contactEmail = null;
3392 	this.contactPhone = null;
3393 	this.contactInfo = "";
3394 	/** Set if the backlink should be displayed. */
3395 	this.backlink = SDK.backlink;
3396 
3397 	/** Support connections to external bots through their web API. */
3398 	this.external = false;
3399 	this.apiURL = null;
3400 	this.apiPost = null;
3401 	this.apiResponse = null;
3402 	
3403 	this.switchText = true;
3404 	this.big = false;
3405 	this.conversation = null;
3406 	this.voiceInit = null;
3407 	this.listen = false;
3408 		
3409 	/**
3410 	 * Create an embedding bar and div in the current webpage.
3411 	 */
3412 	this.createBox = function() {
3413 		if (this.livechatPrefix == null) {
3414 			if (this.version >= 6.0) {
3415 				this.livechatPrefix = "chat";
3416 			} else {
3417 				this.livechatPrefix = "livechat";
3418 			}
3419 		}
3420 		if (this.prefix == "" && this.elementPrefix != null) {
3421 			this.prefix = this.elementPrefix;
3422 		}
3423 		if (this.caption == null) {
3424 			this.caption = this.instanceName;
3425 		}
3426 		var backgroundstyle = "";
3427 		var buttonstyle = "";
3428 		var buttonHoverStyle = "";
3429 		var hidden = "hidden";
3430 		var border = "";
3431 		if (this.backgroundIfNotChrome && SDK.isChrome()) {
3432 			this.background = null;
3433 		}
3434 		if (this.background != null) {
3435 			backgroundstyle = " style='background-color:" + this.background + "'";
3436 			hidden = "visible";
3437 			border = "border:1px;border-style:solid;border-color:black;";
3438 		}
3439 		if (this.color != null) {
3440 			buttonstyle = "background-color:" + this.color + ";";
3441 		}
3442 		if (this.hoverColor != null) {
3443 			buttonHoverStyle = "background-color:" + this.hoverColor + ";";
3444 		}
3445 		var minWidth = "";
3446 		var divWidth = "";
3447 		var background = "";
3448 		var minHeight = "";
3449 		var divHeight = "";
3450 		var maxDivWidth = "";
3451 		var maxHeight = null;
3452 		var responseWidth = "";
3453 		var chatWidth = "";
3454 		var hideAvatar = "";
3455 		var avatarWidth = this.width;
3456 		var minAvatarWidth = "";
3457 		var scrollerHeight = this.height;
3458 		var scrollerMinHeight = "";
3459 		if (this.width != null) {
3460 			if (typeof this.width === "string") {
3461 				this.width = parseInt(width);
3462 			}
3463 			// Auto correct for a short window or screen (assuming square).
3464 			// 250 is total box height minus avatar.
3465 			if ((this.width + 250) > window.innerHeight) {
3466 				avatarWidth = window.innerHeight - 250;
3467 				if (avatarWidth < 100) {
3468 					hideAvatar = "display:none";
3469 				}
3470 			}
3471 			minWidth = "width:" + this.width + "px;";
3472 			minAvatarWidth = "width:" + avatarWidth + "px;";
3473 			background = "background-size:" + avatarWidth + "px auto;";
3474 			divWidth = minWidth;
3475 			divHeight = "min-height:" + avatarWidth + "px;";
3476 			responseWidth = "width:" + (this.width - 16) + "px;";
3477 			chatWidth = "width:" + this.width + "px;";
3478 			maxDivWidth = "max-width:" + (this.width - 50) + "px;";
3479 			scrollerHeight = avatarWidth;
3480 		}
3481 		if (this.height != null) {
3482 			if (typeof this.height === "string") {
3483 				this.height = parseInt(height);
3484 			}
3485 			minHeight = "height:" + this.height + "px;";
3486 			divHeight = minHeight;
3487 			if (this.width != null) {
3488 				background = "background-size:" + this.width + "px " + this.height + "px;";
3489 			} else {
3490 				background = "background-size: auto " + this.height + "px;";
3491 				divWidth = "min-width:" + this.height + "px;";
3492 				responseWidth = "width:" + (this.height - 16) + "px;";
3493 				chatWidth = "width:" + this.height + "px;";
3494 			}
3495 		} else {
3496 			scrollerMinHeight = "height:" + scrollerHeight + "px;";
3497 		}
3498 		var inputFont = "";
3499 		if (SDK.isMobile()) {
3500 			inputFont = "font-size: 16px;";
3501 		}
3502 		var boxloc = "bottom:10px;right:10px";
3503         if (this.boxLocation == "top-left") {
3504             boxloc = "top:10px;left:10px";
3505         } else if (this.boxLocation == "top-right") {
3506             boxloc = "top:10px;right:10px";
3507         } else if (this.boxLocation == "bottom-left") {
3508             boxloc = "bottom:10px;left:10px";
3509         } else if (this.boxLocation == "bottom-right") {
3510             boxloc = "bottom:10px;right:10px";
3511         }
3512         var locationBottom = 20;
3513         if (this.version < 6.0 || this.prefix != "botplatform") {
3514         	locationBottom = 2;
3515         }
3516         var boxbarloc = "bottom:" + (locationBottom + this.verticalOffset) + "px;right:" + this.offset + "px";
3517         if (this.boxLocation == "top-left") {
3518             boxbarloc = "top:" + (locationBottom + this.verticalOffset) + "px;left:" + this.offset + "px";
3519         } else if (this.boxLocation == "top-right") {
3520             boxbarloc = "top:" + (locationBottom + this.verticalOffset) + "px;right:" + this.offset + "px";
3521         } else if (this.boxLocation == "bottom-left") {
3522             boxbarloc = "bottom:" + (locationBottom + this.verticalOffset) + "px;left:" + this.offset + "px";
3523         } else if (this.boxLocation == "bottom-right") {
3524             boxbarloc = "bottom:" + (locationBottom + this.verticalOffset) + "px;right:" + this.offset + "px";
3525         }
3526 		var box = document.createElement('div');	
3527 		var html =
3528 			"<style>\n"
3529 				+ "." + this.prefix + "box { position:fixed;" + boxloc + ";z-index:152;margin:2px;display:none;" + border + " }\n"
3530 				+ "." + this.prefix + "boxmenu { visibility:" + hidden + "; }\n"
3531 				+ (this.forceStyles ? "#" : ".") + "" + this.prefix + "boxbarmax { font-size:18px;margin:2px;padding:0px;text-decoration:none; }\n"
3532 				+ "." + this.prefix + "boxbar { position:fixed;" + boxbarloc + ";z-index:152;margin:0;padding:6px;" + buttonstyle + " }\n"
3533 				+ "." + this.prefix + "boxbar:hover { " + buttonHoverStyle + " }\n"
3534 				+ "." + this.prefix + "no-bubble-text { " + responseWidth + "; max-height:100px; overflow:auto; }\n"
3535 				+ "#" + this.prefix + "contactinfo { " + minHeight + minWidth + " }\n"
3536 				+ "." + this.prefix + "contactconnect { margin:4px;padding:8px;color:white;text-decoration:none;" + buttonstyle + " }\n"
3537 				+ "." + this.prefix + "scroller { overflow-x:hidden;" + scrollerMinHeight + minWidth + " }\n"
3538 				+ "." + this.prefix + "bubble-text { " + responseWidth + "; max-height:100px; overflow:auto; }\n"
3539 				+ "." + this.prefix + "chatchat-1-div { " + maxDivWidth + "}\n"
3540 				+ "." + this.prefix + "chatchat-2-div { " + maxDivWidth + "}\n"
3541 				+ (this.forceStyles ? "#" : ".") + this.prefix + "chat { width:99%;min-height:22px; }\n"
3542 				+ "." + this.prefix + "box:hover ." + this.prefix + "boxmenu { visibility:visible; }\n";
3543 		if (this.version < 6.0 || this.prefix != "botplatform") {
3544 			html = html
3545 				+ "." + this.prefix + "box:hover { border:1px;border-style:solid;border-color:black; }\n"
3546 				+ "." + this.prefix + "box:hover ." + this.prefix + "boxmenu { visibility:visible; }\n"
3547 				+ "." + this.prefix + "boxclose, ." + this.prefix + "boxmin, ." + this.prefix + "boxmax { font-size:22px;margin:2px;padding:0px;text-decoration:none; }\n"
3548 				+ "." + this.prefix + "boxclose:hover, ." + this.prefix + "boxmin:hover, ." + this.prefix + "boxmax:hover { color: #fff;background: grey; }\n"
3549 				+ "." + this.prefix + "no-bubble { margin:4px; padding:8px; border:1px; border-style:solid; border-color:black; background-color:white; color:#585858; }\n"
3550 				+ "." + this.prefix + "no-bubble-plain { margin:4px; padding:8px; border:1px; }\n"
3551 				+ "#" + this.prefix + "contactinfo span { margin-left:4px;margin-top:4px; }\n"
3552 				+ "#" + this.prefix + "contactinfo input { margin:4px;font-size:13px;height:33px;width:90%;border:1px solid #d5d5d5; }\n"
3553 				+ "." + this.prefix + "boxbutton { width:20px;height:20px;margin:4px; }\n"
3554 				+ "." + this.prefix + "menupopup div { position:absolute;margin: -1px 0 0 0;padding: 3px 3px 3px 3px;background: #fff;border-style:solid;border-color:black;border-width:1px;width:180px;max-width:300px;z-index:152;visibility:hidden;opacity:0;transition:visibility 0s linear 0.3s, opacity 0.3s linear; }\n"
3555 				+ "." + this.prefix + "menupopup:hover div { display:inline;visibility:visible;opacity:1;transition-delay:0.5s; }\n"
3556 				+ "a." + this.prefix + "menuitem { text-decoration: none;display: block;color: #585858; }\n"
3557 				+ "a." + this.prefix + "menuitem:hover { color: #fff;background: grey; }\n"
3558 				+ "tr." + this.prefix + "menuitem:hover { background: grey; }\n"
3559 				+ "." + this.prefix + "yandex { display:none; }\n"
3560 				+ "." + this.prefix + "chatpowered { margin:4px;color:grey;font-size:10px; }\n"
3561 				+ "img." + this.prefix + "menu { width: 24px;height: 24px;margin: 2px;cursor: pointer;vertical-align: middle; }\n"
3562 				+ "span." + this.prefix + "menu { color: #818181;font-size: 12px; }\n"
3563 				+ "." + this.prefix + "bubble-div { padding-bottom:15px;position:relative; }\n"
3564 				+ "." + this.prefix + "bubble { margin:4px; padding:8px; border:1px; border-style:solid; border-color:black; border-radius:10px; background-color:white; color:#585858; }\n"
3565 				+ "." + this.prefix + "bubble:before { content:''; position:absolute; bottom:0px; left:40px; border-width:20px 0 0 20px; border-style:solid; border-color:black transparent; display:block; width:0;}\n"
3566 				+ "." + this.prefix + "bubble:after { content:''; position:absolute; bottom:3px; left:42px; border-width:18px 0 0 16px; border-style:solid; border-color:white transparent; display:block; width:0;}\n"
3567 				+ "." + this.prefix + "box-input-span { display:block; overflow:hidden; margin:4px; padding-right:4px; }\n"
3568 				+ "#" + this.prefix + "boxtable { background:none; border:none; margin:0; }\n"
3569 				+ "#" + this.prefix + "showChatLog { display:none; }\n"
3570 				+ "#" + this.prefix + "showChatLogButton { display:none; }\n"
3571 				+ "#" + this.prefix + "boxbar3 { display:none; }\n"
3572 				+ "#" + this.prefix + "boxbarmax { color: white; }\n";
3573 		}
3574 		html = html + this.style
3575 			+ "</style>\n"
3576 			+ "<div id='" + this.prefix + "box' class='" + this.prefix + "box' " + backgroundstyle + ">"
3577 				+ "<div class='" + this.prefix + "boxmenu'>"
3578 					+ (this.backlink ? "<span class='" + this.prefix + "chatpowered'>powered by <a href='" + SDK.backlinkURL + "' target='_blank'>" + SDK.NAME + "</a></span>" : "")
3579 					+ "<span style='float:right'><a id='" + this.prefix + "boxmin' class='" + this.prefix + "boxmin' onclick='return false;' href='#'><img src='" + SDK.url + "/images/minimize.png'></a> ";
3580 		if (this.showBoxmax) {
3581 			html = html + "<a id='" + this.prefix + "boxmax' class='" + this.prefix + "boxmax' onclick='return false;' href='#'><img src='" + SDK.url + "/images/open.png'> </a></span><br/>";
3582 		} else {
3583 			html = html + "</span><br/>";
3584 		}
3585 		html = html + "</div>";
3586 		html = html
3587 			+ "<div id='" + this.prefix + "online' class='" + this.prefix + "online'>"
3588 			+ "</div>"
3589 			+ "<div id='" + this.prefix + "scroller' class='" + this.prefix + "scroller'>"
3590 			+ "<table id='" + this.prefix + "console' class='" + this.prefix + "console' width=100% cellspacing=2></table>"
3591 			+ "</div>";
3592 		html = html
3593 			+ "<div id='" + this.prefix + "avatar-div' style='" + hideAvatar + "'>"
3594 				+ "<div id='" + this.prefix + "avatar-image-div' style='display:none;text-align:center;" + minHeight + minAvatarWidth + "'>"
3595 					+ "<img id='" + this.prefix + "avatar' style='" + minHeight + minAvatarWidth + "'/>"
3596 				+ "</div>"
3597 				+ "<div id='" + this.prefix + "avatar-video-div' style='display:none;text-align:center;" + divHeight + divWidth + background + "background-repeat: no-repeat;'>"
3598 					+ "<video id='" + this.prefix + "avatar-video' autoplay preload='auto' style='background:transparent;" + minHeight + minAvatarWidth + "'>"
3599 						+ "Video format not supported by your browser (try Chrome)"
3600 					+ "</video>"
3601 				+ "</div>"
3602 				+ "<div id='" + this.prefix + "avatar-canvas-div' style='display:none;text-align:center;" + divHeight + divWidth + "'>"
3603 					+ "<canvas id='" + this.prefix + "avatar-canvas' style='background:transparent;" + minHeight + minAvatarWidth + "'>"
3604 						+ "Canvas not supported by your browser (try Chrome)"
3605 					+ "</canvas>"
3606 				+ "</div>"
3607 				+ "<div id='" + this.prefix + "avatar-game-div' style='display:none;text-align:center;" + divHeight + divWidth + "'>"
3608 				+ "</div>"
3609 			+ "</div>";
3610 		
3611 		var urlprefix = this.connection.credentials.url + "/";
3612 		html = html
3613 				+ "<div>"
3614 					+ "<div " + (this.bubble ? "id='" + this.prefix + "bubble-div'" : "") + " " + (this.bubble ? "class='" + this.prefix + "bubble-div'" : "") + ">"
3615 					+ "<div class='" + this.prefix + "" + (this.bubble ? "bubble" : (this.background == null ? "no-bubble" : "no-bubble-plain") )
3616 					+ "'><div class='" + this.prefix + (this.bubble ? "bubble-text" : "no-bubble-text" ) + "'>"
3617 						+ "<span id='" + this.prefix + "response'>" + (this.greeting == null ? this.loading : this.greeting) + "</span><br/>"
3618 					+ "</div></div></div>"
3619 					+ "<div><span class='" + this.prefix + "box-input-span'><input id='" + this.prefix + "chat' type='text' class='" + this.prefix + "box-input'/></span></div>";
3620 		if (this.showMenubar) {
3621 			html = html
3622 				+ "<div>"
3623 				+ "<span class='" + this.prefix + "menu'>\n"
3624 				+ "<div style='inline-block;position:relative'>\n"
3625 				+ "<span class='" + this.prefix + "menupopup'>"
3626 				+ "<div style='text-align:left;bottom:30px'>"
3627 				+ "<table>\n";
3628 				if (this.showChooseLanguage) {
3629 					html = html
3630 					+ "<tr class='" + this.prefix + "menuitem'>"
3631 					+ "<td><a id='" + this.prefix + "boxlanguagemenu' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + urlprefix + "images/language.png' title='Translate to and from your selected language'>"
3632 						+ " <select id='" + this.prefix + "chooselanguage'>"
3633 							+ "<option value='none'>Choose Language</option>"
3634 							+ "<option value='en'>English</option>"
3635 							+ "<option value='zh'>Chinese</option>"
3636 							+ "<option value='es'>Spanish</option>"
3637 							+ "<option value='pt'>Portuguese</option>"
3638 							+ "<option value='de'>German</option>"
3639 							+ "<option value='fr'>French</option>"
3640 							+ "<option value='ja'>Japanese</option>"
3641 							+ "<option value='ar'>Arabic</option>"
3642 							+ "<option value='none'>None</option>"
3643 							+ "<option value='none'></option>"
3644 							+ "<option value='af'>Afrikaans</option>"
3645 							+ "<option value='sq'>Albanian</option>"
3646 							+ "<option value='hy'>Armenian</option>"
3647 							+ "<option value='az'>Azerbaijani</option>"
3648 							+ "<option value='ba'>Bashkir</option>"
3649 							+ "<option value='eu'>Basque</option>"
3650 							+ "<option value='be'>Belarusian</option>"
3651 							+ "<option value='bn'>Bengali</option>"
3652 							+ "<option value='bs'>Bosnian</option>"
3653 							+ "<option value='bg'>Bulgarian</option>"
3654 							+ "<option value='ca'>Catalan</option>"
3655 							+ "<option value='za'>Chinese</option>"
3656 							+ "<option value='hr'>Croatian</option>"
3657 							+ "<option value='cs'>Czech</option>"
3658 							+ "<option value='da'>Danish</option>"
3659 							+ "<option value='nl'>Dutch</option>"
3660 							+ "<option value='en'>English</option>"
3661 							+ "<option value='et'>Estonian</option>"
3662 							+ "<option value='fi'>Finnish</option>"
3663 							+ "<option value='fr'>French</option>"
3664 							+ "<option value='gl'>Galician</option>"
3665 							+ "<option value='ka'>Georgian</option>"
3666 							+ "<option value='de'>German</option>"
3667 							+ "<option value='gu'>Gujarati</option>"
3668 							+ "<option value='ht'>Haitian</option>"
3669 							+ "<option value='he'>Hebrew</option>"
3670 							+ "<option value='hi'>Hindi</option>"
3671 							+ "<option value='hu'>Hungarian</option>"
3672 							+ "<option value='id'>Indonesian</option>"
3673 							+ "<option value='ga'>Irish</option>"
3674 							+ "<option value='it'>Italian</option>"
3675 							+ "<option value='ja'>Japanese</option>"
3676 							+ "<option value='kn'>Kannada</option>"
3677 							+ "<option value='kk'>Kazakh</option>"
3678 							+ "<option value='ky'>Kirghiz</option>"
3679 							+ "<option value='ko'>Korean</option>"
3680 							+ "<option value='la'>Latin</option>"
3681 							+ "<option value='lv'>Latvian</option>"
3682 							+ "<option value='lt'>Lithuanian</option>"
3683 							+ "<option value='mk'>Macedonian</option>"
3684 							+ "<option value='mg'>Malagasy</option>"
3685 							+ "<option value='ms'>Malay</option>"
3686 							+ "<option value='mt'>Maltese</option>"
3687 							+ "<option value='mn'>Mongolian</option>"
3688 							+ "<option value='no'>Norwegian</option>"
3689 							+ "<option value='fa'>Persian</option>"
3690 							+ "<option value='pl'>Polish</option>"
3691 							+ "<option value='pt'>Portuguese</option>"
3692 							+ "<option value='pa'>Punjabi</option>"
3693 							+ "<option value='ro'>Romanian</option>"
3694 							+ "<option value='ru'>Russian</option>"
3695 							+ "<option value='sr'>Serbian</option>"
3696 							+ "<option value='si'>Sinhalese</option>"
3697 							+ "<option value='sk'>Slovak</option>"
3698 							+ "<option value='es'>Spanish</option>"
3699 							+ "<option value='sw'>Swahili</option>"
3700 							+ "<option value='sv'>Swedish</option>"
3701 							+ "<option value='tl'>Tagalog</option>"
3702 							+ "<option value='tg'>Tajik</option>"
3703 							+ "<option value='ta'>Tamil</option>"
3704 							+ "<option value='tt'>Tatar</option>"
3705 							+ "<option value='th'>Thai</option>"
3706 							+ "<option value='tr'>Turkish</option>"
3707 							+ "<option value='uk'>Ukrainian</option>"
3708 							+ "<option value='ur'>Urdu</option>"
3709 							+ "<option value='uz'>Uzbek</option>"
3710 							+ "<option value='cy'>Welsh</option>"
3711 					  	+ "</select>"
3712 					+ "</a></td>"
3713 					+ "</tr>\n";
3714 				}
3715 			if (this.allowFiles) {
3716 				if (this.showSendImage) {
3717 					html = html
3718 						+ "<tr class='" + this.prefix + "menuitem'>"
3719 							+ "<td><a id='" + this.prefix + "sendImage' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + urlprefix + "/images/image.png' title='Resize and send an image attachment'> Send image</a></td>"
3720 						+ "</tr>\n"
3721 						+ "<tr class='" + this.prefix + "menuitem'>"
3722 							+ "<td><a id='" + this.prefix + "sendAttachment' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + urlprefix + "/images/attach.png' title='Send an image or file attachment'> Send file</a></td>"
3723 						+ "</tr>\n";
3724 				}
3725 			}
3726 			html = html
3727 				+ "<tr id='" + this.prefix + "showChatLog' class='" + this.prefix + "menuitem'>"
3728 					+ "<td><a class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + urlprefix + "/images/chat_log.png' title='Chat log'> Chat Log</a></td>"
3729 				+ "</tr>\n"
3730 				+ "<tr id='" + this.prefix + "showAvatarBot' class='" + this.prefix + "menuitem' style='display:none;'>"
3731 					+ "<td><a class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img class='" + this.prefix + "menu' src='" + urlprefix + "/images/avatar-icon.png' title='Avatar Bot'> Show Avatar</a></td>"
3732 				+ "</tr>\n";
3733 			
3734 			if (this.allowSpeech) {
3735 				html = html
3736 					+ "<tr class='" + this.prefix + "menuitem'>"
3737 						+ "<td><a id='" + this.prefix + "boxspeakmenu' class='" + this.prefix + "menuitem' onclick='return false;' href='#'><img id='" + this.prefix + "boxspeak2' class='" + this.prefix + "menu' src='" + urlprefix + (this.speak ? "images/sound.png": "images/mute.png") +"' title='Speech'> Speech</a></td>"
3738 					+ "</tr>\n";
3739 				if (SDK.isChrome()) {
3740 					html = html
3741 						+ "<tr class='" + this.prefix + "menuitem'>"
3742 							+ "<td><a id='" + this.prefix + "boxspeakrecognitionmenu' class='" + this.prefix + "menuitem' onclick='return false;' href='#'>"
3743 									+ "<img id='" + this.prefix + "boxspeakrecognition2' class='" + this.prefix + "menu' src='" + urlprefix + "/images/micoff.png' title='Speech recognition (browser must support HTML5 speech recognition, such as Chrome)'> Speech Recognition</a>"
3744 							+ "</td>"
3745 						+ "</tr>\n"
3746 				}
3747 			}
3748 			html = html
3749 				+ "</table>\n"
3750 				+ "</div>"
3751 				+ "<img id='" + this.prefix + "boxmenubutton' class='" + this.prefix + "boxbutton' src='" + urlprefix + "/images/menu.png'>";
3752 				if (this.showChooseLanguage) {
3753 					html = html + "<img id='" + this.prefix + "boxlanguage' class='" + this.prefix + "boxbutton' src='" + urlprefix + "/images/language.png'>";
3754 				}
3755 				html = html + "</span>";
3756 			if (this.allowSpeech) {
3757 				html = html
3758 					+ "<a onclick='return false;' href='#' title='Speech'><img id='" + this.prefix + "boxspeak' class='" + this.prefix + "boxbutton' src='"
3759 									+ urlprefix
3760 									+ (this.speak ? "images/sound.png": "images/mute.png") +"'></a></td>"
3761 						+ (SDK.isChrome() ?
3762 								("<a onclick='return false;' href='#' title='Speech Recognition'><img id='" + this.prefix + "boxspeakrecognition' class='" + this.prefix + "boxbutton' src='"
3763 										+ urlprefix
3764 										+ "images/micoff.png'></a>")
3765 								: "");
3766 			}
3767 			html = html + "<a id='" + this.prefix + "showChatLogButton' onclick='return false;' href='#' title='Show chat log'>"
3768 					+ "<img class='" + this.prefix + "boxbutton' src='" + urlprefix + "/images/chat_log.png' title='Chat log'></a>"
3769 					+ "<a id='" + this.prefix + "showAvatarBotButton' style='display:none;' onclick='return false;' href='#' title='Show Avatar Bot'>"
3770 					+ "<img class='" + this.prefix + "boxbutton' src='" + urlprefix + "/images/avatar-icon.png' title='Avatar Bot'></a>";
3771 		}
3772 		if (this.allowFiles && this.showFileButtons) {
3773 			html = html
3774 				+ "<a id='" + this.prefix + "sendImageTool' onclick='return false;' href='#'><img class='" + this.prefix + "boxbutton' src='" + urlprefix + "/images/image.png' title='Resize and send an image attachment'></a>"
3775 				+ "<a id='" + this.prefix + "sendAttachmentTool' onclick='return false;' href='#'><img class='" + this.prefix + "boxbutton' src='" + urlprefix + "/images/attach.png' title='Send an image or file attachment'></a>";
3776 		}
3777 		html = html
3778 			+ "</span>"
3779 			+ "</div>"
3780 			+ "</div>"
3781 			+ "<div id='" + this.prefix + "yandex' class='" + this.prefix + "yandex'><span>Powered by <a target='_blank' href='http://translate.yandex.com/'>Yandex.Translate</a></span></div>"
3782 			+ "</div>"
3783 			+ "</div>"
3784 			+ "<div id='" + this.prefix + "boxbar' class='" + this.prefix + "boxbar'>"
3785 				+ "<div id='" + this.prefix + "boxbar2' class='" + this.prefix + "boxbar2'>"
3786 					+ "<span><a id='" + this.prefix + "boxbarmax' class='" + this.prefix + "boxbarmax' " + " onclick='return false;' href='#'><img id='" + this.prefix + "boxbarmaximage' " + "src='" + SDK.url + "/images/maximizew.png'> " + this.caption + " </a>"
3787 					+ " <a id='" + this.prefix + "boxclose' class='" + this.prefix + "boxclose' onclick='return false;' onclick='return false;' href='#'> <img src='" + SDK.url + "/images/closeg.png'></a></span><br/>"
3788 				+ "</div>";
3789 		html = html
3790 				+ "<div id='" + this.prefix + "boxbar3' class='" + this.prefix + "boxbar3'" + ">"
3791 					+ "<span><a id='" + this.prefix + "boxbarmax2' class='" + this.prefix + "boxbarmax2' " + (this.forceStyles ? "style='color:white' " : "") + " onclick='return false;' href='#'>" + "</a></span><br>"
3792 					+ " <a id='" + this.prefix + "boxclose2' class='" + this.prefix + "boxclose2' onclick='return false;' onclick='return false;' href='#'> <img src='" + SDK.url + "/images/closeg.png'></a></span><br/>"
3793 				+ "</div>\n"
3794 			+ "</div>\n";
3795 		
3796 		if (this.promptContactInfo) {
3797 			html = html
3798 				+ "<div id='" + this.prefix + "contactinfo' class='" + this.prefix + "box' " + backgroundstyle + ">"
3799 					+ "<div class='" + this.prefix + "boxmenu'>"
3800 						+ "<span style='float:right'><a id='" + this.prefix + "contactboxmin' class='" + this.prefix + "contactboxmin' onclick='return false;' href='#'><img src='" + SDK.url + "/images/minimize.png'></a>"
3801 					+ "</div>\n"
3802 					+ "<div style='margin:10px'>\n"
3803 						+ "<span>Name</span><br/><input id='" + this.prefix + "contactname' type='text' /><br/>\n"
3804 						+ "<span>Email</span><br/><input id='" + this.prefix + "contactemail' type='email' /><br/>\n"
3805 						+ "<span>Phone</span><br/><input id='" + this.prefix + "contactphone' type='text' /><br/>\n"
3806 						+ "<br/><a id='" + this.prefix + "contactconnect' class='" + this.prefix + "contactconnect' " + (this.forceStyles ? "style='color:white' " : "") + " onclick='return false;' href='#'>Connect</a>\n"
3807 					+ "<br/><br/></div>\n"
3808 				+ "</div>";
3809 		}
3810 		
3811 		box.innerHTML = html;
3812 		document.body.appendChild(box);
3813 		
3814 		var self = this;
3815 		document.getElementById(this.prefix + "chat").addEventListener("keypress", function(event) {
3816 			if (event.keyCode == 13) {
3817 				self.sendMessage();
3818 				return false;
3819 			}
3820 		});
3821 		document.getElementById(this.prefix + "boxclose").addEventListener("click", function() {
3822 			self.closeBox();
3823 			return false;
3824 		});
3825 		document.getElementById(this.prefix + "boxclose2").addEventListener("click", function() {
3826 			self.closeBox();
3827 			return false;
3828 		});
3829 		document.getElementById(this.prefix + "boxmin").addEventListener("click", function() {
3830 			self.minimizeBox();
3831 			return false;
3832 		});
3833 		if (this.promptContactInfo) {
3834 			document.getElementById(this.prefix + "contactboxmin").addEventListener("click", function() {
3835 				self.minimizeBox();
3836 				return false;
3837 			});
3838 			document.getElementById(this.prefix + "contactconnect").addEventListener("click", function() {
3839 				self.contactConnect();
3840 				return false;
3841 			});
3842 		}
3843 		if (document.getElementById(this.prefix + "boxmax") != null) {
3844 			document.getElementById(this.prefix + "boxmax").addEventListener("click", function() {
3845 				self.popup();
3846 				return false;
3847 			});
3848 		}
3849 		document.getElementById(this.prefix + "boxbarmax").addEventListener("click", function() {
3850 			self.maximizeBox();
3851 			return false;
3852 		});
3853 		
3854 		document.getElementById(this.prefix + "boxbarmax2").addEventListener("click", function() {
3855 			self.maximizeBox();
3856 			return false;
3857 		});
3858 		
3859 		var langOrig = null;
3860 		var nativeVoiceOrig = null;
3861 		var voiceOrig = null;
3862 		if (document.getElementById(this.prefix + "chooselanguage") != null) {
3863 			document.getElementById(this.prefix + "chooselanguage").addEventListener("change", function() {
3864 				if (nativeVoiceOrig == null && langOrig == null) {
3865 					langOrig = self.lang;
3866 					nativeVoiceOrig = self.nativeVoice;
3867 					voiceOrig = self.voice;
3868 				}
3869 				var element = document.getElementById(self.prefix + 'chooselanguage');
3870 				self.lang = element.value;
3871 				if (self.lang != "none") {
3872 					document.getElementById(self.prefix + 'yandex').style.display = "inline";
3873 					self.nativeVoice = true;
3874 					self.translate = true;
3875 					self.voice = null;
3876 				} else {
3877 					document.getElementById(self.prefix + 'yandex').style.display = "none";
3878 					self.translate = false;
3879 					self.lang = langOrig;
3880 					self.nativeVoice = nativeVoiceOrig;
3881 					self.voice = voiceOrig;
3882 				}
3883 			});
3884 		}
3885 		if (document.getElementById(this.prefix + "sendImage") != null) {
3886 			document.getElementById(this.prefix + "sendImage").addEventListener("click", function() {
3887 				self.sendImage();
3888 				return false;
3889 			});
3890 		}
3891 		if (document.getElementById(this.prefix + "sendAttachment") != null) {
3892 			document.getElementById(this.prefix + "sendAttachment").addEventListener("click", function() {
3893 				self.sendAttachment();
3894 				return false;
3895 			});
3896 		}
3897 		if (document.getElementById(this.prefix + "sendImageTool") != null) {
3898 			document.getElementById(this.prefix + "sendImageTool").addEventListener("click", function() {
3899 				self.sendImage();
3900 				return false;
3901 			});
3902 		}
3903 		if (document.getElementById(this.prefix + "sendAttachmentTool") != null) {
3904 			document.getElementById(this.prefix + "sendAttachmentTool").addEventListener("click", function() {
3905 				self.sendAttachment();
3906 				return false;
3907 			});
3908 		}
3909 
3910 		
3911 		if (this.avatar && this.chatLog) {
3912 			document.getElementById(this.prefix + "online").style.display = "none";
3913 			document.getElementById(this.prefix + "scroller").style.display = "none";
3914 			if (this.version >= 6.0 && this.prefix == "botplatform") {
3915 				var chatLogDiv = document.getElementById(this.prefix + "showChatLog");
3916 				if (chatLogDiv != null) {
3917 					chatLogDiv.style.display = "block";
3918 				}
3919 				var chatLogButtonDiv = document.getElementById(this.prefix + "showChatLogButton");
3920 				if (chatLogButtonDiv != null) {
3921 					chatLogButtonDiv.style.display = "inline-block";
3922 				}
3923 			}
3924 		} else if (this.avatar && !this.chatLog) {
3925 			document.getElementById(this.prefix + "online").style.display = "none";
3926 			document.getElementById(this.prefix + "scroller").style.display = "none";
3927 			var chatLogDiv = document.getElementById(this.prefix + "showChatLog");
3928 			var chatLogButtonDiv = document.getElementById(this.prefix + "showChatLogButton");
3929 			if (chatLogDiv != null) {
3930 				chatLogDiv.style.display = "none";
3931 			}
3932 			if (chatLogButtonDiv != null) {
3933 				chatLogButtonDiv.style.display = "none";
3934 			}
3935 		} else if (!this.avatar && this.chatLog) {
3936 			document.getElementById(this.prefix + "online").style.display = "inline";
3937 			document.getElementById(this.prefix + "scroller").style.display = "inline-block";
3938 			document.getElementById(this.prefix + "avatar-div").style.display = "none";
3939 			var chatLogDiv = document.getElementById(this.prefix + "showChatLog");
3940 			var chatLogButtonDiv = document.getElementById(this.prefix + "showChatLogButton");
3941 			if (chatLogDiv != null) {
3942 				chatLogDiv.style.display = "none";
3943 			}
3944 			if (chatLogButtonDiv != null) {
3945 				chatLogButtonDiv.style.display = "none";
3946 			}
3947 			var bubbleDiv = document.getElementById(this.prefix + "bubble-div");
3948 			if (bubbleDiv != null) {
3949 				bubbleDiv.style.display = "none";	
3950 			}
3951 			var noBubblePlain = document.getElementsByClassName(this.prefix + "no-bubble-plain");
3952 			if (noBubblePlain != null && noBubblePlain.length != 0) {
3953 				noBubblePlain[0].style.display = "none";
3954 			}
3955 		} else {
3956 			document.getElementById(this.prefix + "online").style.display = "none";
3957 			document.getElementById(this.prefix + "scroller").style.display = "none";
3958 			document.getElementById(this.prefix + "avatar-div").style.display = "none";
3959 		}
3960 		
3961 		if (document.getElementById(this.prefix + "showChatLog") != null) {
3962 			document.getElementById(this.prefix + "showChatLog").addEventListener("click", function() {
3963 				self.showChatLog();
3964 				return false;
3965 			});
3966 		}
3967 		if (document.getElementById(this.prefix + "showChatLogButton") != null) {
3968 			document.getElementById(this.prefix + "showChatLogButton").addEventListener("click", function() {
3969 				self.showChatLog();
3970 				return false;
3971 			});
3972 		}
3973 		if (document.getElementById(this.prefix + "showAvatarBot") != null) {
3974 			document.getElementById(this.prefix + "showAvatarBot").addEventListener("click", function() {
3975 				self.showAvatarBot();
3976 				return false;
3977 			});
3978 		}
3979 		if (document.getElementById(this.prefix + "showAvatarBotButton") != null) {
3980 			document.getElementById(this.prefix + "showAvatarBotButton").addEventListener("click", function() {
3981 				self.showAvatarBot();
3982 				return false;
3983 			});
3984 		}
3985 		this.showChatLog = function() {
3986 			document.getElementById(this.prefix + "avatar-div").style.display = "none";
3987 			document.getElementById(this.prefix + "online").style.display = "inline";
3988 			var bubbleDiv = document.getElementById(this.prefix + "bubble-div");
3989 			if (bubbleDiv != null) {
3990 				bubbleDiv.style.display = "none";
3991 			}
3992 			var noBubblePlain = document.getElementsByClassName(this.prefix + "no-bubble-plain");
3993 			if (noBubblePlain != null && noBubblePlain.length != 0) {
3994 				noBubblePlain[0].style.display = "none";
3995 			}
3996 			document.getElementById(this.prefix + "scroller").style.display = "inline-block";
3997 			if (this.version >= 6.0 && this.prefix == "botplatform") {
3998 				document.getElementById(this.prefix + "showChatLog").style.display = "none";
3999 				document.getElementById(this.prefix + "showChatLogButton").style.display = "none";
4000 				document.getElementById(this.prefix + "showAvatarBot").style.display = "block";
4001 				document.getElementById(this.prefix + "showAvatarBotButton").style.display = "inline-block";
4002 			}
4003 		}
4004 		this.showAvatarBot  = function() {
4005 			document.getElementById(this.prefix + "online").style.display = "none";
4006 			document.getElementById(this.prefix + "scroller").style.display = "none";
4007 			document.getElementById(this.prefix + "avatar-div").style.display = "inline-block";
4008 			var bubbleDiv = document.getElementById(this.prefix + "bubble-div");
4009 			if (bubbleDiv != null) {
4010 				bubbleDiv.style.display = "inherit";
4011 			}
4012 			var noBubblePlain = document.getElementsByClassName(this.prefix + "no-bubble-plain");
4013 			if (noBubblePlain != null && noBubblePlain.length != 0) {
4014 				noBubblePlain[0].style.display = "inherit";
4015 			}
4016 			if (this.version >= 6.0 && this.prefix == "botplatform") {
4017 				document.getElementById(this.prefix + "showChatLog").style.display = "block";
4018 				document.getElementById(this.prefix + "showChatLogButton").style.display = "inline-block";
4019 				document.getElementById(this.prefix + "showAvatarBot").style.display = "none";
4020 				document.getElementById(this.prefix + "showAvatarBotButton").style.display = "none";
4021 			}
4022 		}
4023 		
4024 		if (document.getElementById(this.prefix + "boxspeak") != null) {
4025 			document.getElementById(this.prefix + "boxspeak").addEventListener("click", function() {
4026 				self.speak = !self.speak;
4027 				var urlprefix = self.connection.credentials.url + "/";
4028 				if (self.speak) {
4029 					document.getElementById(self.prefix + "boxspeak").src = urlprefix + "images/sound.png";
4030 					document.getElementById(self.prefix + "boxspeak2").src = urlprefix + "images/sound.png";
4031 				} else {
4032 					document.getElementById(self.prefix + "boxspeak").src = urlprefix + "images/mute.png";
4033 					document.getElementById(self.prefix + "boxspeak2").src = urlprefix + "images/mute.png";
4034 				}
4035 				return false;
4036 			});
4037 			document.getElementById(this.prefix + "boxspeakmenu").addEventListener("click", function() {
4038 				self.speak = !self.speak;
4039 				var urlprefix = self.connection.credentials.url + "/";
4040 				if (self.speak) {
4041 					document.getElementById(self.prefix + "boxspeak").src = urlprefix + "images/sound.png";
4042 					document.getElementById(self.prefix + "boxspeak2").src = urlprefix + "images/sound.png";
4043 				} else {
4044 					document.getElementById(self.prefix + "boxspeak").src = urlprefix + "images/mute.png";
4045 					document.getElementById(self.prefix + "boxspeak2").src = urlprefix + "images/mute.png";
4046 				}
4047 				return false;
4048 			});
4049 		}
4050 		if (document.getElementById(this.prefix + "boxspeakrecognition") != null) {
4051 			SDK.registerSpeechRecognition(document.getElementById(self.prefix + 'chat'), function() {
4052 				self.sendMessage();
4053 			});
4054 			document.getElementById(this.prefix + "boxspeakrecognition").addEventListener("click", function() {
4055 				self.listen = !self.listen;
4056 				if (self.listen) {
4057 					SDK.startSpeechRecognition();
4058 					document.getElementById(self.prefix + 'boxspeakrecognition').src = urlprefix + "images/mic.png";
4059 					document.getElementById(self.prefix + 'boxspeakrecognition2').src = urlprefix + "images/mic.png";
4060 				} else {
4061 					SDK.stopSpeechRecognition();
4062 					document.getElementById(self.prefix + 'boxspeakrecognition').src = urlprefix + "images/micoff.png";
4063 					document.getElementById(self.prefix + 'boxspeakrecognition2').src = urlprefix + "images/micoff.png";
4064 				}
4065 			});
4066 			document.getElementById(this.prefix + "boxspeakrecognitionmenu").addEventListener("click", function() {
4067 				self.listen = !self.listen;
4068 				if (self.listen) {
4069 					SDK.startSpeechRecognition();
4070 					document.getElementById(self.prefix + 'boxspeakrecognition').src = urlprefix + "images/mic.png";
4071 					document.getElementById(self.prefix + 'boxspeakrecognition2').src = urlprefix + "images/mic.png";
4072 				} else {
4073 					SDK.stopSpeechRecognition();
4074 					document.getElementById(self.prefix + 'boxspeakrecognition').src = urlprefix + "images/micoff.png";
4075 					document.getElementById(self.prefix + 'boxspeakrecognition2').src = urlprefix + "images/micoff.png";
4076 				}
4077 			});
4078 		}
4079 	}
4080 	
4081 	/**
4082 	 * Create a live chat bar beside the bot bar.
4083 	 */
4084 	this.createLiveChatBox = function(channel, label, position) {
4085 		var box = document.createElement('div');
4086 		var buttonstyle = "";
4087 		if (this.color != null) {
4088 			buttonstyle = "background-color:" + this.color + ";";
4089 		}
4090 		var buttonHoverStyle = "";
4091 		if (this.hoverColor != null) {
4092 			buttonHoverStyle = "background-color:" + this.hoverColor + ";";
4093 		}
4094 		if (label == null) {
4095 			label = "Live Chat";
4096 		}
4097 		if (position == null) {
4098 			position = (this.caption.length + label.length) * 8;
4099 			position = "right:" + position + "px";
4100 		}
4101 		var html =
4102 			"<style>\n"
4103 				+ "." + this.prefix + this.livechatPrefix + "boxbar { position:fixed;bottom:2px;" + position + ";z-index:152;margin:0;padding:6px;" + buttonstyle + " }\n"
4104 				+ "." + this.prefix + this.livechatPrefix + "boxbar:hover { " + buttonHoverStyle + " }\n"
4105 				+ (this.forceStyles ? "#" : ".") + this.prefix + this.livechatPrefix + "boxmax { color:white;font-size:18px;margin:2px;padding:0px;text-decoration:none; }\n"
4106 			+ "</style>\n"
4107 			+ "<div id='" + this.prefix + this.livechatPrefix + "boxbar' class='" + this.prefix + this.livechatPrefix + "boxbar'>"
4108 			+ "<span><a id='" + this.prefix + this.livechatPrefix + "boxmax' class='" + this.prefix + this.livechatPrefix + "boxmax' onclick='return false;' href='#'>" + label + "</a></span>"
4109 			+ "</div>";
4110 		
4111 		box.innerHTML = html;
4112 		document.body.appendChild(box);
4113 		
4114 		document.getElementById(this.prefix + this.livechatPrefix + "boxmax").addEventListener("click", function() {
4115 			SDK.popupwindow(SDK.url + '/livechat?id=' + channel + '&embedded&chat','child', 700, 520);
4116 			return false;
4117 		});
4118 	}
4119 	
4120 	/**
4121 	 * Minimize the embedding div in the current webpage.
4122 	 */
4123 	this.minimizeBox = function() {
4124 		if (this.promptContactInfo) {
4125 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
4126 		}
4127 		document.getElementById(this.prefix + "box").style.display = 'none';
4128 		document.getElementById(this.prefix + "boxbar").style.display = 'inline';
4129 		var livechatbot = document.getElementById(this.prefix + this.livechatPrefix + "boxbar");
4130 		if (livechatbot != null) {
4131 			livechatbot.style.display = 'inline';
4132 		}
4133 		this.exit();
4134 		return false;		
4135 	}
4136 
4137 	/**
4138 	 * Check contact info and connect.
4139 	 */
4140 	this.contactConnect = function() {
4141 		this.hasContactInfo = true;
4142 		this.contactName = document.getElementById(this.prefix + "contactname").value;
4143 		var ok = true;
4144 		if (this.contactName != null && this.contactName == "") {
4145 			ok = false;
4146 			document.getElementById(this.prefix + "contactname").style.borderColor = "red";
4147 			document.getElementById(this.prefix + "contactname").placeholder = "Enter name";
4148 		}
4149 		this.contactEmail = document.getElementById(this.prefix + "contactemail").value;
4150 		if (this.contactEmail != null && this.contactEmail.indexOf("@") == -1) {
4151 			ok = false;
4152 			document.getElementById(this.prefix + "contactemail").style.borderColor = "red";
4153 			document.getElementById(this.prefix + "contactemail").placeholder = "Enter valid email";
4154 		}
4155 		this.contactPhone = document.getElementById(this.prefix + "contactphone").value;
4156 		this.contactInfo = this.contactName + " " + this.contactEmail + " " + this.contactPhone;
4157 		if (ok) {
4158 			this.maximizeBox();
4159 		}
4160 	}
4161 	
4162 	/**
4163 	 * Maximize the embedding div in the current webpage.
4164 	 */
4165 	this.maximizeBox = function() {
4166 		if (this.promptContactInfo && !this.hasContactInfo) {
4167 			document.getElementById(this.prefix + "contactinfo").style.display = 'inline';
4168 			document.getElementById(this.prefix + "boxbar").style.display = 'none';
4169 			document.getElementById(this.prefix + "box").style.display = 'none';
4170 			var livechatbot = document.getElementById(this.prefix + this.livechatPrefix + "boxbar");
4171 			if (livechatbot != null) {
4172 				livechatbot.style.display = 'none';
4173 			}
4174 		} else {
4175 			if (this.promptContactInfo) {
4176 				document.getElementById(this.prefix + "contactinfo").style.display = 'none';
4177 			}
4178 			document.getElementById(this.prefix + "boxbar").style.display = 'none';
4179 			document.getElementById(this.prefix + "box").style.display = 'inline';
4180 			var livechatbot = document.getElementById(this.prefix + this.livechatPrefix + "boxbar");
4181 			if (livechatbot != null) {
4182 				livechatbot.style.display = 'none';
4183 			}
4184 			this.greet();
4185 		}
4186 		return false;		
4187 	}
4188 	
4189 	/**
4190 	 * Close the embedding div in the current webpage.
4191 	 */
4192 	this.closeBox = function() {
4193 		if (this.promptContactInfo) {
4194 			document.getElementById(this.prefix + "contactinfo").style.display = 'none';
4195 		}
4196 		document.getElementById(this.prefix + "boxbar").style.display = 'none';
4197 		document.getElementById(this.prefix + "box").style.display = 'none';
4198 		var livechatbot = document.getElementById(this.prefix + this.livechatPrefix + "boxbar");
4199 		if (livechatbot != null) {
4200 			livechatbot.style.display = 'none';
4201 		}
4202 		this.exit();
4203 		return false;		
4204 	}
4205 	
4206 	/**
4207 	 * Create a popup window chat session with the bot.
4208 	 */
4209 	this.popup = function() {
4210 		var box = document.getElementById(this.prefix + "box");
4211 		if (box != null) {
4212 			box.style.display = 'none';
4213 		}
4214 		var speech = this.speak;
4215 		if (!this.allowSpeech) {
4216 			speech = "disable";
4217 		}
4218 		var height = 520;
4219 		if (!this.avatar && !this.chatLog) {
4220 			height = 260;
4221 		}
4222 		if (this.popupURL != null) {
4223 			var popupURL = this.popupURL;
4224 			if (popupURL.indexOf("chat?") != -1 && this.contactInfo != null && this.contactInfo != "") {
4225 				popupURL = popupURL + "&info=" + encodeURI(this.contactInfo);
4226 			}
4227 			SDK.popupwindow(popupURL, 'child', 700, height);
4228 		} else {			
4229 			var form = document.createElement("form");
4230             form.setAttribute("method", "post");
4231             form.setAttribute("action", SDK.url + "/chat");
4232             form.setAttribute("target", 'child');
4233  
4234             var input = document.createElement('input');
4235             input.type = 'hidden';
4236             input.name = "id";
4237             input.value = this.instance;
4238             form.appendChild(input);
4239  
4240             input = document.createElement('input');
4241             input.type = 'hidden';
4242             input.name = "embedded";
4243             input.value = "embedded";
4244             form.appendChild(input);
4245  
4246             input = document.createElement('input');
4247             input.type = 'hidden';
4248             input.name = "speak";
4249             input.value = speech;
4250             form.appendChild(input);
4251  
4252             input = document.createElement('input');
4253             input.type = 'hidden';
4254             input.name = "avatar";
4255             input.value = this.avatar;
4256             form.appendChild(input);
4257  
4258             input = document.createElement('input');
4259             input.type = "hidden";
4260             input.name = "css";
4261             input.value = this.css;
4262             form.appendChild(input);
4263  
4264             input = document.createElement('input');
4265             input.type = 'hidden';
4266             input.name = "info";
4267             input.value = this.contactInfo;
4268             form.appendChild(input);
4269  
4270             input = document.createElement('input');
4271             input.type = 'hidden';
4272             input.name = "application";
4273             input.value = this.connection.credentials.applicationId;
4274             form.appendChild(input);
4275             
4276             document.body.appendChild(form);
4277             
4278 			SDK.popupwindow('','child', 700, height);
4279 			
4280 			form.submit();
4281             document.body.removeChild(form);
4282 		}
4283 		this.minimizeBox();
4284 		return false;
4285 	}
4286 
4287 	this.sendImage = function() {
4288 		if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
4289 			alert('The File APIs are not fully supported in this browser.');
4290 			return false;
4291 		}
4292 		var form = document.createElement("form");
4293 		form.enctype = "multipart/form-data";
4294 		form.method = "post";
4295 		form.name = "fileinfo";
4296 		var fileInput = document.createElement("input");
4297 		var self = this;
4298 		fileInput.name = "file";
4299 		fileInput.type = "file";
4300 		form.appendChild(fileInput);
4301 		fileInput.onchange = function() {
4302 			var file = fileInput.files[0];
4303 			self.sendFileAttachment(file, true, form);
4304 		}
4305 		fileInput.click();
4306 		return false;
4307 	};
4308 	
4309 	this.sendAttachment = function() {
4310 		if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
4311 			alert('The File APIs are not fully supported in this browser.');
4312 			return false;
4313 		}
4314 		var form = document.createElement("form");
4315 		form.enctype = "multipart/form-data";
4316 		form.method = "post";
4317 		form.name = "fileinfo";
4318 		var fileInput = document.createElement("input");
4319 		var self = this;
4320 		fileInput.name = "file";
4321 		fileInput.type = "file";
4322 		form.appendChild(fileInput);
4323 		fileInput.onchange = function() {
4324 			var file = fileInput.files[0];
4325 			self.sendFileAttachment(file, false, form);
4326 		}
4327 		fileInput.click();
4328 		return false;
4329 	};
4330 
4331 	this.sendFileAttachment = function(file, resize, form) {
4332 		var self = this;
4333 		var media = new MediaConfig();
4334 		if (this.instance == null) {
4335 			this.connection.error("Missing instance property");
4336 			return false;
4337 		}
4338 		media.instance = this.instance;
4339 		media.name = file.name;
4340 		media.type = file.type;
4341 		if (!resize && file.size > SDK.MAX_FILE_UPLOAD) {
4342 			this.connection.error("File exceeds maximum upload size of " + (SDK.MAX_FILE_UPLOAD / 1000000) + "meg");
4343 		} else {
4344 			this.connection.createBotAttachment(media, file, resize, form, function(media) {
4345 				//var message = "file: " + file.name + " : " + file.type + " : " + self.connection.fetchLink(media.file);
4346 				var message = self.connection.fetchLink(media.file);
4347 				message = document.getElementById(self.prefix + 'chat').value + " " + message;
4348 				document.getElementById(self.prefix + 'chat').value = message;
4349 				//self.sendMessage();
4350 			})
4351 		}
4352 		return false;
4353 	};
4354 	
4355 	/**
4356 	 * Search for link using <a href="chat:yes">...
4357 	 * Switch them to use an onclick to post the chat back to the bot.
4358 	 */
4359 	this.linkChatPostbacks = function(node) {
4360 		var self = this;
4361         var links = node.getElementsByTagName("a");
4362         for (var index = 0; index < links.length; index++) {
4363         	var a = links[index];
4364         	var href = a.getAttribute("href");
4365         	if (href != null && href.indexOf("chat:") != -1) {
4366         		var chat = href.substring("chat:".length, href.length).trim();
4367         		var temp = function(param, element) {
4368         			element.onclick = function() {
4369 	        			self.sendMessage(param);
4370 	        			return false;
4371 	        		};
4372         		}
4373         		temp(chat, a);
4374         	}
4375         }
4376         var buttons = node.getElementsByTagName("button");
4377         for (var index = 0; index < buttons.length; index++) {
4378         	var button = buttons[index];
4379         	if (button.parentNode.nodeName == "A") {
4380         		continue;
4381         	}
4382         	var chat = button.textContent;
4383         	if (chat != null && chat.length > 0) {
4384         		var temp = function(param, element) {
4385         			element.onclick = function() {
4386 	        			self.sendMessage(param);
4387 	        			return false;
4388 	        		};
4389         		}
4390         		temp(chat, button);
4391         	}
4392         }
4393         var choices = node.getElementsByTagName("select");
4394         for (var index = 0; index < choices.length; index++) {
4395         	var choice = choices[index];
4396     		var temp = function(param) {
4397     			param.addEventListener("change", function() {
4398         			self.sendMessage(param.value);
4399         			return false;
4400         		});
4401     		}
4402     		temp(choice);
4403         }
4404 	}
4405 	
4406 	/**
4407 	 * A chat message was received from the bot.
4408 	 */
4409 	this.response = function(user, message) {
4410 		var responseDiv = document.getElementById(this.prefix + 'response');
4411 		if (responseDiv != null) {
4412 			responseDiv.innerHTML = SDK.linkURLs(message);
4413 			this.linkChatPostbacks(responseDiv);
4414 			if (!SDK.secure) {
4415 				SDK.evalScripts(responseDiv);
4416 			}
4417 			this.message(user, message);
4418 			if (this.focus) {
4419 				var chat = document.getElementById(this.prefix + 'chat');
4420 				if (chat != null) {
4421 					chat.focus();
4422 				}
4423 			}
4424 		}
4425 		if (this.onresponse != null) {
4426 			this.onresponse(message);
4427 		}
4428 		if (responseDiv != null) {
4429 			var self = this;
4430 			// Fix Chrome bug,
4431 			if (SDK.fixChromeResizeCSS && SDK.isChrome()) {
4432 				var padding = responseDiv.parentNode.parentNode.style.padding;
4433 				responseDiv.parentNode.parentNode.style.padding = "7px";
4434 				setTimeout(function() {
4435 					responseDiv.parentNode.parentNode.style.padding = padding;
4436 				}, 10);
4437 			}
4438 		}
4439 	}
4440 	
4441 	/**
4442 	 * A chat message was received from the bot.
4443 	 */
4444 	this.message = function(user, message) {
4445 		var speaker = user;
4446 		var scroller = document.getElementById(this.prefix + 'scroller');
4447 		var chatconsole = document.getElementById(this.prefix + 'console');
4448 		if (scroller == null || chatconsole == null) {
4449 			return;
4450 		}
4451 		var tr = document.createElement('tr');
4452 		tr.style.verticalAlign = "top";
4453 		var td = document.createElement('td');
4454 		var td2 = document.createElement('td');
4455 		var div = document.createElement('div');
4456 		var div2 = document.createElement('div');
4457 		var span = document.createElement('span');
4458 		var span2 = document.createElement('span');
4459 		var br = document.createElement('br');
4460 		var chatClass = this.prefix + 'chatchat-1';
4461 		div.className = this.prefix + 'chatchat-1-div';
4462 		div2.className = this.prefix + 'chatchat-1-div-2';
4463 		span.className = this.prefix + 'chatchat-user-1';
4464 		td.className = this.prefix + 'chat-user-1';
4465 		var userImg;
4466 		if (speaker === this.botThumb.name) {
4467 			userImg = document.createElement('img');
4468 			userImg.className = this.prefix + 'chat-user';
4469 			userImg.setAttribute('alt', this.botThumb.name);
4470 			userImg.setAttribute('src', this.botThumb.avatar);
4471 			td.appendChild(userImg);
4472 		} else {
4473 			td.className = this.prefix + 'chat-user-2';
4474 			chatClass = this.prefix + 'chatchat-2';
4475 			div.className = this.prefix + 'chatchat-2-div';
4476 			div2.className = this.prefix + 'chatchat-2-div-2';
4477 			span.className = this.prefix + 'chatchat-user-2';
4478 			userImg = document.createElement('img');
4479 			userImg.className = this.prefix + 'chat-user';
4480 			userImg.setAttribute('alt', this.userThumb.name);
4481 			userImg.setAttribute('src', this.userThumb.avatar);
4482 			td.appendChild(userImg);
4483 		}
4484 		td.setAttribute('nowrap', 'nowrap');
4485 		td2.className = chatClass;
4486 		td2.setAttribute('align', 'left');
4487 		td2.setAttribute('width', '100%');
4488 		var date = new Date(); 
4489 		var time = date.getHours() + ":" + ((date.getMinutes() < 10)? "0" : "") + date.getMinutes() + ":" + ((date.getSeconds() < 10)? "0" : "") + date.getSeconds();
4490 		span.innerHTML = speaker + " <small>" + time + "</small>";
4491 		span2.className = chatClass;
4492 		span2.innerHTML = SDK.linkURLs(message);
4493 		this.linkChatPostbacks(span2);
4494 		chatconsole.appendChild(tr);
4495 		tr.appendChild(td);
4496 		tr.appendChild(td2);
4497 		div.appendChild(span);
4498 		div.appendChild(br);
4499 		div.appendChild(div2);
4500 		td2.appendChild(div);
4501 		div2.appendChild(span2);
4502 		while (chatconsole.childNodes.length > 500) {
4503 			chatconsole.removeChild(chatconsole.firstChild);
4504 		}
4505 		scroller.scrollTop = scroller.scrollHeight;
4506 	};
4507 
4508 	/**
4509 	 * Update the bot's avatar's image/video/audio from the chat response.
4510 	 */
4511 	this.updateAvatar = function(responseMessage) {
4512 		var urlprefix = this.connection.credentials.url + "/";
4513 		SDK.updateAvatar(responseMessage, this.speak, urlprefix, this.prefix, null, null, this.nativeVoice, this.lang, this.nativeVoiceName);
4514 		if (SDK.commands && responseMessage.command != null) {
4515 			var command = JSON.parse(responseMessage.command);
4516 			if (command.start != null) {
4517 				this.game = new window [command.start]();
4518 				this.initGame(this.game)
4519 			}
4520 		}
4521 		if (this.game != null) {
4522 			this.game.updateAvatar(responseMessage);
4523 		}
4524 		console.log(responseMessage);
4525 	};
4526 	
4527 	this.initGame = function(game) {
4528 		this.game = game;
4529 		game.init(this);
4530 	}
4531 	
4532 	this.toggleSpeak = function() {
4533 		this.speak = !this.speak;
4534 	}
4535 	
4536 	/**
4537 	 * Initialize the bot listener.
4538 	 */
4539 	this.start = function() {
4540 		if (this.prefix == "" && this.elementPrefix != null) {
4541 			this.prefix = this.elementPrefix;
4542 		}
4543 		var self = this;
4544 		this.connection.error = function(message) {
4545 			self.response("Error", message);
4546 		}
4547 		if (this.avatar) {
4548 			var config = new ChatConfig();
4549 			config.instance = this.instance;
4550 			if (this.translate) {
4551 				config.language = this.lang;
4552 			}
4553 			if (this.format != null) {
4554 				config.avatarFormat = this.format;
4555 			}
4556 			if (this.hd != null) {
4557 				config.avatarHD = this.hd;
4558 			}
4559 			this.connection.initChat(config, function(responseMessage) {
4560 				if (this.conversation == null) {
4561 					self.updateAvatar(responseMessage);
4562 				}
4563 			});
4564 		}
4565 		if (this.connection.user != null && this.connection.user.user != null) {
4566 			this.connection.viewUser(this.connection.user, function(user) {
4567 				var userName = user.user;
4568 				var userAvatar = user.avatar;
4569 				self.userThumb['name'] = userName;
4570 				self.userThumb['avatar'] = userAvatar;
4571 			});
4572 		} else {
4573 			var urlprefix = this.connection.credentials.url + "/";
4574 			self.userThumb['name'] = "You:";
4575 			self.userThumb['avatar'] = urlprefix + "images/user-thumb.jpg";
4576 		}
4577 		var instanceConfig = new InstanceConfig();
4578 		instanceConfig.id = this.instance;
4579 		this.connection.fetch(instanceConfig, function(instanceConfig) {
4580 			var botName = instanceConfig.name;
4581 			var botAvatar = instanceConfig.avatar;
4582 			self.botThumb['name'] = botName;
4583 			self.botThumb['avatar'] = botAvatar;
4584 			var onlineDiv = document.getElementById(self.prefix + 'online');
4585 			if (onlineDiv == null) {
4586 				return;
4587 			}
4588 			if (!onlineDiv.hasChildNodes()) {
4589 				var div = document.createElement('div');
4590 				div.className = "online-user";
4591 				var botImage = document.createElement('img');
4592 				botImage.className = self.prefix + "chat-bot";
4593 				botImage.setAttribute('alt', self.botThumb.name);
4594 				botImage.setAttribute('src', self.botThumb.avatar);
4595 				var span = document.createElement('span');
4596 				span.className = self.prefix + "user-bot";
4597 				span.innerHTML = self.botThumb.name;
4598 				div.appendChild(botImage);
4599 				div.appendChild(span);
4600 				onlineDiv.appendChild(div);
4601 				var line = document.createElement('hr');
4602 				onlineDiv.appendChild(line);
4603 			}
4604 		});
4605 	}
4606 	
4607 	/**
4608 	 * Send the bot an empty message to let it greet the user.
4609 	 * This will have the bot respond with any defined greeting it has.
4610 	 */
4611 	this.greet = function() {
4612 		this.start();
4613 		var chat = new ChatConfig();
4614 		chat.info = this.contactInfo;
4615 		chat.instance = this.instance;
4616 		if (this.greetingMessage != null) {
4617 			chat.message = this.greetingMessage;
4618 		}
4619 		if (this.translate) {
4620 			chat.language = this.lang;
4621 		}
4622 		if (this.avatarId != null) {
4623 			chat.avatar = this.avatarId;
4624 		}
4625 		if (this.hd != null) {
4626 			chat.avatarHD = this.hd;
4627 		} else if ((this.width != null && this.width > 400) || (this.height != null && this.height > 400)) {
4628 			chat.avatarHD = true;
4629 		}
4630 		if (this.format != null) {
4631 			chat.avatarFormat = this.format;
4632 		}
4633 		if (this.nativeVoice && SDK.speechSynthesis) {
4634 			chat.speak = false;
4635 		} else {
4636 			chat.speak = this.speak;
4637 		}
4638 		var self = this;
4639 		if (this.external) {
4640 			this.externalChat(chat);
4641 		} else {
4642 			this.connection.chat(chat, function(responseMessage) {
4643 				self.conversation = responseMessage.conversation;
4644 				self.updateAvatar(responseMessage);
4645 				if (responseMessage.message != null && responseMessage.message != "") {
4646 					self.response(self.instanceName, responseMessage.message);
4647 				} else if (self.greeting == null) {
4648 					document.getElementById(self.prefix + 'response').innerHTML = "Hi";
4649 				}
4650 			});
4651 		}
4652 		return false;
4653 	};
4654 	
4655 	/**
4656 	 * Send the current text from the chat input as a message to the bot, and process the response.
4657 	 */
4658 	this.sendMessage = function(message) {
4659 		if (message == null) {
4660 			var chat = document.getElementById(this.prefix + 'chat');
4661 			if (chat != null) {
4662 				message = chat.value;
4663 			}
4664 		}
4665 		if (message != '') {
4666 			this.message(this.userName, message);
4667 			var chat = new ChatConfig();
4668 			chat.message = message;
4669 			chat.instance = this.instance;
4670 			if (this.translate) {
4671 				chat.language = this.lang;
4672 			}
4673 			if (this.avatarId != null) {
4674 				chat.avatar = this.avatarId;
4675 			}
4676 			if (this.hd != null) {
4677 				chat.avatarHD = this.hd;
4678 			} else if ((this.width != null && this.width > 400) || (this.height != null && this.height > 400)) {
4679 				chat.avatarHD = true;
4680 			}
4681 			if (this.format != null) {
4682 				chat.avatarFormat = this.format;
4683 			}
4684 			if (this.nativeVoice && SDK.speechSynthesis) {
4685 				chat.speak = false;
4686 			} else {
4687 				chat.speak = this.speak;
4688 			}
4689 			chat.conversation = this.conversation;
4690 			var correction = document.getElementById('correction');
4691 			if (correction != null && correction.checked) {
4692 				chat.correction = true;
4693 				correction.checked = false;
4694 			}
4695 			var learning = document.getElementById('learning');
4696 			if (learning != null && learning.style.display != "none") {
4697 				chat.learn = learning.checked;
4698 			}
4699 			var debug = document.getElementById('debug');
4700 			if (debug != null && debug.checked) {
4701 				chat.debug = true;
4702 				var debugLevel = document.getElementById('debugLevel');
4703 				if (debugLevel != null) {
4704 					chat.debugLevel = debugLevel.value;
4705 				}
4706 			}
4707 			var offensive = document.getElementById('offensive');
4708 			if (offensive != null && offensive.checked) {
4709 				chat.offensive = true;
4710 				offensive.checked = false;
4711 			}
4712 			var emote = document.getElementById('emote');
4713 			if (emote != null && emote.value != null && emote.value != "" && emote.value != "NONE") {
4714 				chat.emote = emote.value.toUpperCase();
4715 				emote.value = "NONE";
4716 			}
4717 			var action = document.getElementById('action');
4718 			if (action != null && action.value != null && action.value != "") {
4719 				chat.action = action.value;
4720 				action.value = "";
4721 			}
4722 			var responseDiv = document.getElementById(this.prefix + 'response');
4723 			if (responseDiv != null) {
4724 				responseDiv.innerHTML = '<i>thinking</i>';
4725 			}
4726 			var chatInput = document.getElementById(this.prefix + 'chat');
4727 			if (chatInput != null) {
4728 				chatInput.value = '';
4729 			}
4730 			var self = this;
4731 			if (this.external) {
4732 				this.externalChat(chat);
4733 			} else {
4734 				this.connection.chat(chat, function(responseMessage) {
4735 					self.conversation = responseMessage.conversation;
4736 					self.response(self.instanceName, responseMessage.message);
4737 					self.updateAvatar(responseMessage);
4738 					var log = document.getElementById('log');
4739 					var logText = responseMessage.log;
4740 					if (log != null) {
4741 						if (logText != null) {
4742 							log.style.display = "inline";
4743 							log.innerHTML = logText;
4744 						} else {
4745 							log.innerHTML = "";
4746 						}
4747 					}
4748 				});
4749 			}
4750 		}
4751 		return false;
4752 	};
4753 	
4754 	/**
4755 	 * Send the json command to the bot, and process the response.
4756 	 */
4757 	this.sendCommand = function(json, processor) {
4758 		if (json != '') {
4759 			var command = new CommandConfig();
4760 			command.command = json;
4761 			command.instance = this.instance;
4762 			if (this.translate) {
4763 				command.language = this.lang;
4764 			}
4765 			if (this.avatarId != null) {
4766 				command.avatar = this.avatarId;
4767 			}
4768 			if (this.hd != null) {
4769 				command.avatarHD = this.hd;
4770 			} else if ((this.width != null && this.width > 400) || (this.height != null && this.height > 400)) {
4771 				command.avatarHD = true;
4772 			}
4773 			if (this.format != null) {
4774 				command.avatarFormat = this.format;
4775 			}
4776 			if (this.nativeVoice && SDK.speechSynthesis) {
4777 				command.speak = false;
4778 			} else {
4779 				command.speak = this.speak;
4780 			}
4781 			command.conversation = this.conversation;
4782 			var correction = document.getElementById('correction');
4783 			if (correction != null && correction.checked) {
4784 				command.correction = true;
4785 				correction.checked = false;
4786 			}
4787 			var learning = document.getElementById('learning');
4788 			if (learning != null && learning.style.display != "none") {
4789 				command.learn = learning.checked;
4790 			}
4791 			var debug = document.getElementById('debug');
4792 			if (debug != null && debug.checked) {
4793 				command.debug = true;
4794 				var debugLevel = document.getElementById('debugLevel');
4795 				if (debugLevel != null) {
4796 					chat.debugLevel = debugLevel.value;
4797 				}
4798 			}
4799 			var offensive = document.getElementById('offensive');
4800 			if (offensive != null && offensive.checked) {
4801 				command.offensive = true;
4802 				offensive.checked = false;
4803 			}
4804 			var emote = document.getElementById('emote');
4805 			if (emote != null && emote.value != null && emote.value != "" && emote.value != "NONE") {
4806 				command.emote = emote.value.toUpperCase();
4807 				emote.value = "NONE";
4808 			}
4809 			var action = document.getElementById('action');
4810 			if (action != null && action.value != null && action.value != "") {
4811 				command.action = action.value;
4812 				action.value = "";
4813 			}
4814 			var responseDiv = document.getElementById(this.prefix + 'response');
4815 			if (responseDiv != null) {
4816 				responseDiv.innerHTML = '<i>thinking</i>';
4817 			}
4818 			var chatInput = document.getElementById(this.prefix + 'chat');
4819 			if (chatInput != null) {
4820 				chatInput.value = '';
4821 			}
4822 			var self = this;
4823 			if (this.external) {
4824 				return false;
4825 			} else {
4826 				this.connection.command(command, function(responseMessage) {
4827 					self.conversation = responseMessage.conversation;
4828 					self.response(self.instanceName, responseMessage.message);
4829 					self.updateAvatar(responseMessage);
4830 					var log = document.getElementById('log');
4831 					var logText = responseMessage.log;
4832 					if (log != null) {
4833 						if (logText != null) {
4834 							log.style.display = "inline";
4835 							log.innerHTML = logText;
4836 						} else {
4837 							log.innerHTML = "";
4838 						}
4839 					}
4840 					processor(responseMessage.command);
4841 				});
4842 			}
4843 		}
4844 		return false;
4845 	};
4846 	
4847 	/**
4848 	 * Send an external API chat request.
4849 	 */
4850 	this.externalChat = function(chat) {
4851 		var url = this.apiURL;
4852 		if (chat.message == null) {
4853 			url = url.replace(":message", "");			
4854 		} else {
4855 			url = url.replace(":message", encodeURIComponent(chat.message));
4856 		}
4857 		if (chat.conversation == null) {
4858 			url = url.replace(":conversation", "");
4859 		} else {
4860 			url = url.replace(":conversation", encodeURIComponent(chat.conversation));			
4861 		}
4862 		if (chat.speak) {
4863 			url = url.replace(":speak", "true");
4864 		} else {
4865 			url = url.replace(":speak", "");			
4866 		}
4867 		var self = this;
4868 		this.connection.GET(url, function(xml) {
4869 			if (xml == null) {
4870 				return null;
4871 			}
4872 			var responseMessage = new ChatResponse();
4873 			responseMessage.parseXML(xml);
4874 			self.conversation = responseMessage.conversation;
4875 			self.response(self.instanceName, responseMessage.message);
4876 			var urlprefix = self.apiURL.substring(0, self.apiURL.indexOf("/rest/api/form-chat")) + "/";
4877 			SDK.updateAvatar(responseMessage, self.speak, urlprefix, self.prefix, null, null, self.nativeVoice, self.lang, self.nativeVoiceName);
4878 			var log = document.getElementById('log');
4879 			var logText = responseMessage.log;
4880 			if (log != null) {
4881 				if (logText != null) {
4882 					log.innerHTML = logText;
4883 				} else {
4884 					log.innerHTML = "";
4885 				}
4886 			}
4887 		});
4888 	}
4889 
4890 	/**
4891 	 * Exit the conversation.
4892 	 */
4893 	this.exit = function() {
4894 		if (this.conversation == null || this.external) {
4895 			return false;
4896 		}
4897 		var chat = new ChatConfig();
4898 		chat.disconnect = true;
4899 		chat.instance = this.instance;
4900 		chat.conversation = this.conversation;
4901 		var self = this;
4902 		this.connection.chat(chat, function(responseMessage) {
4903 			self.conversation = null;
4904 		});
4905 		return false;
4906 	};
4907 
4908 	/**
4909 	 * Clear the chat console.
4910 	 */
4911 	this.clear = function() {
4912 		document.getElementById(this.prefix + 'response').innerHTML = '';
4913 		var console = document.getElementById(this.prefix + 'console');
4914 		if (console != null) {
4915 			console.innerHTML = '';
4916 		}
4917 		return false;
4918 	};
4919 
4920 	this.resizeAvatar = function () {
4921 		var avatar = document.getElementById(this.prefix + "avatar");
4922 		var avatarDiv = document.getElementById(this.prefix + "avatar-image-div");
4923 		var avatarVideo = document.getElementById(this.prefix + "avatar-video");
4924 		var avatarVideoDiv = document.getElementById(this.prefix + "avatar-video-div");
4925 		var avatarCanvas = document.getElementById(this.prefix + "avatar-canvas");
4926 		var avatarCanvasDiv = document.getElementById(this.prefix + "avatar-canvas-div");
4927 		var scroller = document.getElementById(this.prefix + "scroller");
4928 		var chatBubbleDiv = document.getElementById(web.prefix + 'bubble-div');
4929 		var chatBubble = document.getElementById(this.prefix + 'bubble');
4930 		if (!this.big) {
4931 			this.hd = true;
4932 			if (avatar != null) {
4933 				avatar.className = "avatar-big";
4934 			}
4935 			if (avatarVideo != null) {
4936 				avatarVideo.className = "avatar-video-big";
4937 			}
4938 			if (avatarVideoDiv != null) {
4939 				avatarVideoDiv.className = "avatar-video-div-big";
4940 			}
4941 			if (avatarCanvas != null) {
4942 				avatarCanvas.className = "avatar-canvas-big";
4943 			}
4944 			if (avatarCanvasDiv != null) {
4945 				avatarCanvasDiv.className = "avatar-canvas-div-big";
4946 			}
4947 			if (scroller != null) {
4948 				scroller.style.display = "none";
4949 				document.getElementById(this.prefix + 'response').style.display = "inline";
4950 			}
4951 			if (chatBubbleDiv != null) {
4952 				chatBubbleDiv.style.display = "block";
4953 			}
4954 			if (chatBubble != null) {
4955 				chatBubble.style.display = "inherit";
4956 			}
4957 			this.big = true;
4958 		} else {
4959 			this.hd = false;
4960 			if (avatar != null) {
4961 				avatar.className = "avatar";
4962 			}
4963 			if (avatarVideo != null) {
4964 				avatarVideo.className = "avatar-video";
4965 			}
4966 			if (avatarVideoDiv != null) {
4967 				avatarVideoDiv.className = "avatar-video-div";
4968 			}
4969 			if (avatarCanvas != null) {
4970 				avatarCanvas.className = "avatar-canvas";
4971 			}
4972 			if (avatarCanvasDiv != null) {
4973 				avatarCanvasDiv.className = "avatar-canvas-div";
4974 			}
4975 			if (scroller != null) {
4976 				if (this.chatLog && window.innerWidth > 480) {
4977 					scroller.style.display = "inline-block";
4978 				} else {
4979 					scroller.style.display = "none";
4980 				}
4981 			}
4982 			if (chatBubbleDiv != null) {
4983 				if (!this.chatLog || window.innerWidth < 480) {
4984 					chatBubbleDiv.style.display = "block";
4985 				} else {
4986 					chatBubbleDiv.style.display = "none";
4987 				}
4988 			}
4989 			if (chatBubble != null) {
4990 				if (!this.chatLog || window.innerWidth < 480) {
4991 					chatBubble.style.display = "block";
4992 				} else {
4993 					chatBubble.style.display = "none";
4994 				}
4995 			}
4996 			this.big = false;
4997 		}
4998 		if (window.onresize != null) {
4999 			setTimeout(window.onresize(), 100);
5000 		}
5001 		return false;
5002 	}
5003 }
5004 
5005 /**
5006  * The WebAvatar provides access to an avatar and binds it to elements in an HTML document.
5007  * It lets you use a bot avatar without having a bot.  You can tell the avatar what to say, and what actions and poses to display.
5008  * The HTML document requires the following elements:
5009  * <ul>
5010  * <li> avatar - img element for the avatar
5011  * <li> avatar-image-div - div element for the avatar's image
5012  * <li> avatar-video - video element for the avatar's video
5013  * <li> avatar-video-div - div element for the avatar's video
5014  * </ul>
5015  * If a prefix is set, these id will be prefixed by the prefix.
5016  * Or you can call createBox() to have the WebAvatar create its own components in the current page.
5017  * @class
5018  */
5019 function WebAvatar() {
5020 	/** Enable or disable speech. */
5021 	this.speak = true;
5022 	/** Configure if the browser's native voice TTS should be used. */
5023 	this.nativeVoice = false;
5024 	/** Set the language for the native voice. */
5025 	this.lang = null;
5026 	/** Set the voice for the native voice. */
5027 	this.nativeVoiceName = null;
5028 	/** An SDK connection object must be set. */
5029 	this.connection = null;
5030 	/** The id or name of the avatar object to use. */
5031 	this.avatar = null;
5032 	/** The name of the voice to use. */
5033 	this.voice = null;
5034 	/** The name of the voice mod to use. */
5035 	this.voiceMod = null;
5036 	/** Allow the background color to be set. */
5037 	this.background = null;
5038 	/** Avatar image/video width. */
5039 	this.width = 300;
5040 	/** Avatar image/video height. */
5041 	this.height = null;
5042 	/** Only apply the background color if not Chrome. */
5043 	this.backgroundIfNotChrome = false;
5044 	/** An optional close event. */
5045 	this.onclose = null;
5046 	/** Return if the avatar box is in a closed state. */
5047 	this.closed = true;
5048 	/** Element id and class prefix. Can be used to have multiple avatars in the same page, or avoid naming collisions. */
5049 	this.prefix = "avatar-";
5050 	/** Store list of messages to output. */
5051 	this.messages = null;
5052 	/** Function to invoke when processing all messages is complete. */
5053 	this.ended = null;
5054 	/** Set if the avatar should request HD (high def) video/images. */
5055 	this.hd = null;
5056 	/** Set if the avatar should request a specific video or image format. */
5057 	this.format = null;
5058 	
5059 	/**
5060 	 * Create an embedding bar and div in the current webpage.
5061 	 */
5062 	this.createBox = function() {
5063 		if (this.prefix == "" && this.elementPrefix != null) {
5064 			this.prefix = this.elementPrefix;
5065 		}
5066 		var backgroundstyle = "";
5067 		var hidden = "hidden";
5068 		var border = "";
5069 		if ((this.background != null) && (!this.backgroundIfNotChrome || !SDK.isChrome())) {
5070 			backgroundstyle = " style='background-color:" + this.background + "'";
5071 			hidden = "visible";
5072 			border = "border:1px;border-style:solid;border-color:black;";
5073 		}
5074 		var box = document.createElement('div');
5075 		var minWidth = "";
5076 		var minHeight = "";
5077 		var divWidth = "";
5078 		var divHeight = "";
5079 		var background = "";
5080 		if (this.width != null) {
5081 			minWidth = "width:" + this.width + "px;";
5082 			background = "background-size:" + this.width + "px;";
5083 			divWidth = minWidth;
5084 			divHeight = "min-height:" + this.width + "px;";
5085 		}
5086 		if (this.height != null) {
5087 			minHeight = "height:" + this.height + "px;";
5088 			divHeight = minHeight;
5089 			if (this.width != null) {
5090 				background = "background-size:" + this.width + "px " + this.height + "px;";
5091 			} else {
5092 				background = "background-size: auto " + this.height + "px;";
5093 				divWidth = "min-width:" + this.height + "px;";
5094 			}
5095 		}
5096 		var html =
5097 			"<style>\n"
5098 				+ "." + this.prefix + "avatarbox { position:fixed;bottom:10px;left:10px;z-index:152;margin:2px;" + border + " }\n"
5099 				+ "." + this.prefix + "avatarbox:hover { border:1px;border-style:solid;border-color:black; }\n"
5100 				+ "." + this.prefix + "avatarbox ." + this.prefix + "avatarboxmenu { visibility:" + hidden + "; }\n"
5101 				+ "." + this.prefix + "avatarbox:hover ." + this.prefix + "avatarboxmenu { visibility:visible; }\n"
5102 				+ "img." + this.prefix + "avatarboxclose { margin:4px }\n"
5103 				+ "#" + this.prefix + "avatarboxclose { margin:0px;font-size:26px; }\n"
5104 				+ "#" + this.prefix + "avatarboxclose:hover { color: #fff;background: grey; }\n"
5105 			+ "</style>\n"
5106 			+ "<div id='" + this.prefix + "avatarbox' class='" + this.prefix + "avatarbox' " + backgroundstyle + ">"
5107 				+ "<div class='" + this.prefix + "avatarboxmenu'>"
5108 					+ "<span style='float:right'><a id='" + this.prefix + "avatarboxclose' onclick='return false;' href='#'><img class='" + this.prefix + "avatarboxclose' src='" + SDK.url + "/images/closeg.png'></a></span><br/>"
5109 				+ "</div>"
5110 				+ "<div id='" + this.prefix + "avatar-image-div' style='display:none;" + minWidth + minHeight + "'>"
5111 					+ "<img id='" + this.prefix + "avatar' style='" + minWidth + minHeight + "'/>"
5112 				+ "</div>"
5113 				+ "<div id='" + this.prefix + "avatar-video-div' style='display:none;" + divWidth + divHeight + background + "background-repeat: no-repeat;'>"
5114 					+ "<video id='" + this.prefix + "avatar-video' autoplay preload='auto' style='background:transparent;" + minWidth + minHeight + "'>"
5115 						+ "Video format not supported by your browser (try Chrome)"
5116 					+ "</video>"
5117 				+ "</div>"
5118 				+ "<div id='" + this.prefix + "avatar-canvas-div' style='display:none;" + divWidth + divHeight + "'>"
5119 					+ "<canvas id='" + this.prefix + "avatar-canvas' style='background:transparent;" + minWidth + minHeight + "'>"
5120 						+ "Canvas not supported by your browser (try Chrome)"
5121 					+ "</canvas>"
5122 				+ "</div>"				
5123 			+ "</div>";
5124 		
5125 		box.innerHTML = html;
5126 		document.body.appendChild(box);
5127 		
5128 		var self = this;
5129 		document.getElementById(this.prefix + "avatarboxclose").addEventListener("click", function() {
5130 			self.closeBox();
5131 			return false;
5132 		});
5133 		this.closed = false;
5134 	}
5135 	
5136 	/**
5137 	 * Open the embedding div in the current webpage.
5138 	 */
5139 	this.openBox = function() {
5140 		document.getElementById(this.prefix + "avatarbox").style.display = 'inline';
5141 		this.speak = true;
5142 		this.closed = false;
5143 		return false;		
5144 	}
5145 	
5146 	/**
5147 	 * Close the embedding div in the current webpage.
5148 	 */
5149 	this.closeBox = function() {
5150 		document.getElementById(this.prefix + "avatarbox").style.display = 'none';
5151 		this.speak = false;
5152 		if (this.onclose != null) {
5153 			this.onclose();
5154 		}
5155 		this.closed = true;
5156 		return false;		
5157 	}
5158 
5159 	/**
5160 	 * Update the avatar's image/video/audio from the message response.
5161 	 */
5162 	this.updateAvatar = function(responseMessage, afterFunction) {
5163 		var urlprefix = this.connection.credentials.url + "/";
5164 		SDK.updateAvatar(responseMessage, this.speak, urlprefix, this.prefix, false, afterFunction, this.nativeVoice, this.lang, this.nativeVoiceName);
5165 	};
5166 	
5167 	/**
5168 	 * Add the message to the avatars message queue.
5169 	 * The messages will be spoken when processMessages() is called.
5170 	 */
5171 	this.addMessage = function(message, emote, action, pose) {
5172 		var config = new AvatarMessage();
5173 		config.message = message;
5174 		config.avatar = this.avatar;
5175 		if (this.hd != null) {
5176 			config.hd = this.hd;
5177 		} else if ((this.width != null && this.width > 400) || (this.height != null && this.height > 400)) {
5178 			config.hd = true;
5179 		}
5180 		if (this.format != null) {
5181 			config.format = this.format;
5182 		}
5183 		if (this.nativeVoice && SDK.speechSynthesis) {
5184 			config.speak = false;
5185 		} else {
5186 			config.speak = this.speak;
5187 			config.voice = this.voice;
5188 			config.voiceMod = this.voiceMod;
5189 		}
5190 		config.emote = emote;
5191 		config.action = action;
5192 		config.pose = pose;
5193 		if (this.messages == null) {
5194 			this.messages = [];
5195 		}
5196 		this.messages[this.messages.length] = config;
5197 		return false;
5198 	};
5199 	
5200 	/**
5201 	 * Add the message to the avatars message queue.
5202 	 * The messages will be spoken when runMessages() is called.
5203 	 */
5204 	this.processMessages = function(pause) {
5205 		if (this.messages == null || this.messages.length == 0) {
5206 			if (this.ended != null) {
5207 				this.ended();
5208 			}
5209 			return false;
5210 		}
5211 		if (pause == null) {
5212 			pause = 500;
5213 		}
5214 		var self = this;
5215 		var message = this.messages[0];
5216 		this.messages = this.messages.splice(1, this.messages.length);
5217 		this.connection.avatarMessage(message, function(responseMessage) {
5218 			self.updateAvatar(responseMessage, function() {
5219 				setTimeout(function() {
5220 					self.processMessages(pause);
5221 				}, pause);
5222 			});
5223 		});
5224 		return false;
5225 	}
5226 	
5227 	/**
5228 	 * Have the avatar speak the message with voice and animation.
5229 	 * The function will be called at the end of the speech.
5230 	 */
5231 	this.message = function(message, emote, action, pose, afterFunction) {
5232 		var config = new AvatarMessage();
5233 		config.message = message;
5234 		config.avatar = this.avatar;
5235 		if (this.nativeVoice && SDK.speechSynthesis) {
5236 			config.speak = false;
5237 		} else {
5238 			config.speak = this.speak;
5239 			config.voice = this.voice;
5240 			config.voiceMod = this.voiceMod;
5241 		}
5242 		config.emote = emote;
5243 		config.action = action;
5244 		config.pose = pose;
5245 		var self = this;
5246 		this.connection.avatarMessage(config, function(responseMessage) {
5247 			self.updateAvatar(responseMessage, afterFunction);
5248 		});
5249 		return false;
5250 	};
5251 }
5252 
5253 /**
5254  * Connection class for a Live Chat, or chatroom connection.
5255  * A live chat connection is different than an SDKConnection as it is asynchronous,
5256  * and uses web sockets for communication.
5257  * @class
5258  * @property channel
5259  * @property user
5260  * @property credentials
5261  * @property listener
5262  * @property keepAlive
5263  * @property onMediaStream
5264  * @property onMediaStreamEnded
5265  * @property nick
5266  * @property channelToken
5267  * @property onNewChannel
5268  * @property nick
5269  */
5270 function LiveChatConnection() {
5271 	this.channel = null;
5272 	this.user = null;
5273 	this.contactInfo = null;
5274 	this.credentials = new Credentials();
5275 	this.socket = null;
5276 	this.listener = null;
5277 	this.keepAlive = false;
5278 	this.keepAliveInterval = null;
5279 	this.mediaConnection = null;
5280 	this.onMediaStream = null;
5281 	this.onMediaStreamEnded = null;
5282 	this.nick = null;
5283 	this.channelToken = null;
5284 	this.onNewChannel = null;
5285 	this.onMessageCallbacks = {};
5286 		
5287 	/**
5288 	 * Connect to the live chat server channel.
5289 	 * Validate the user credentials.
5290 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5291 	 */
5292 	this.connect = function(channel, user) {
5293 		if (this.credentials == null) {
5294 			throw "Mising credentials";
5295 		}
5296 		this.channel = channel;
5297 		this.user = user;
5298 		if (this.nick == null && this.user != null) {
5299 			this.nick = this.user.user;
5300 		}
5301 		var host = null;
5302 		if (SDK.scheme == "https") {
5303 			host = "wss://" + this.credentials.host + this.credentials.app + "/live/chat";
5304 		} else {
5305 			host = "ws://" + this.credentials.host + this.credentials.app + "/live/chat";			
5306 		}
5307 		if ('WebSocket' in window) {
5308 			this.socket = new WebSocket(host);
5309 		} else if ('MozWebSocket' in window) {
5310 			this.socket = new MozWebSocket(host);
5311 		} else {
5312 			throw 'Error: WebSocket is not supported by this browser.';
5313 		}
5314 		
5315 		this.listener.connection = this;
5316 		var self = this;
5317 		
5318 		this.socket.onopen = function () {
5319 			if (self.channel != null) {
5320 				var appId = self.credentials.applicationId;
5321 				if (appId == null) {
5322 					appId = '';
5323 				}
5324 				var connectString = "connect " + self.channel.id;
5325 				if (self.user == null) {
5326 					connectString = connectString + " " + appId;
5327 				} else if (user.token == null) {
5328 					connectString = connectString + " " + self.user.user + " " + self.user.password + " " + appId;						
5329 				} else {
5330 					connectString = connectString + " " + self.user.user + " " + self.user.token + " " + appId;						
5331 				}
5332 				if (self.contactInfo != null) {
5333 					connectString = connectString + " @info " + self.contactInfo;
5334 				}
5335 				self.socket.send(connectString);
5336 			}
5337 			self.setKeepAlive(this.keepAlive);
5338 		};
5339 		
5340 		this.socket.onclose = function () {
5341 			self.listener.message("Info: Closed");
5342 			self.listener.closed();
5343 			self.disconnectMedia();
5344 		};
5345 		
5346 		this.socket.onmessage = function (message) {
5347 	    	user = "";
5348 	    	data = message.data;
5349 	    	text = data;
5350 	    	index = text.indexOf(':');
5351 	    	if (index != -1) {
5352 	    		user = text.substring(0, index);
5353 	    		data = text.substring(index + 2, text.length);
5354 	    	}
5355 			if (user == "Media") {
5356 			    data = JSON.parse(data);
5357 
5358 			    if (data.sender == self.nick) {
5359 			    	return;
5360 			    }
5361 			    if (data.channel != self.channelToken) {
5362 			    	return;
5363 			    }
5364 
5365 			    if (self.onMessageCallbacks[data.channel]) {
5366 			    	self.onMessageCallbacks[data.channel](data.message);
5367 			    };
5368 			    return;
5369 			}
5370 			if (user == "Online-xml") {
5371 				self.listener.updateUsersXML(data);
5372 				return;
5373 			}
5374 			if (user == "Online") {
5375 				self.listener.updateUsers(data);
5376 				return;
5377 			}
5378 			if (user == "Channel") {
5379 				self.channelToken = data;
5380 				if (self.onNewChannel != null) {
5381 					self.onNewChannel(data);
5382 				}
5383 				return;
5384 			}
5385 			if (user == "Nick") {
5386 				if (self.nick == null) {
5387 					self.nick = data;
5388 				}
5389 				return;
5390 			}
5391 			
5392 			if (self.keepAlive && user == "Info" && text.contains("pong")) {
5393 				return;
5394 			}
5395 			if (user == "Info") {
5396 				self.listener.info(text);
5397 				return;
5398 			}
5399 			if (user == "Error") {
5400 				self.listener.error(text);
5401 				return;
5402 			}
5403 			self.listener.message(text);
5404 		};
5405 	};
5406 
5407 	/**
5408 	 * Connect to the active channels media feed (video, audio).
5409 	 */
5410 	this.connectMedia = function(mediaChannel, shareAudio, shareVideo) {
5411 		if (this.mediaConnection != null) {
5412 			this.mediaConnection.leave();
5413 		}
5414 		this.mediaConnection = new RTCMultiConnection(mediaChannel);
5415 		var self = this;
5416 		var open = false;
5417 		this.mediaConnection.session = {
5418 		    audio: shareAudio,
5419 		    video: shareVideo
5420 		};
5421 		/*this.mediaConnection.mediaConstraints.audio = {
5422 		    mandatory: {},
5423 		    optional: [{
5424 		        googEchoCancellation: true,
5425 		        googAutoGainControl: true,
5426 		        googNoiseSuppression: true,
5427 		        googHighpassFilter: true,
5428 		        googTypingNoiseDetection: true,
5429 		        googAudioMirroring: true
5430 		    }]
5431 		};*/
5432 		/*this.mediaConnection.privileges = {
5433 		    canStopRemoteStream: true,
5434 		    canMuteRemoteStream: true
5435 		};*/
5436 
5437 		this.mediaConnection.openSignalingChannel = function (config) {
5438 		    var channel = config.channel || this.channel;
5439 		    self.onMessageCallbacks[channel] = config.onmessage;
5440 
5441 		    if (config.onopen) {
5442 		    	setTimeout(config.onopen, 1000);
5443 		    }
5444 
5445 		    // directly returning socket object using "return" statement
5446 		    return {
5447 		        send: function (message) {
5448 		            self.socket.send("Media: " + JSON.stringify({
5449 		                sender: self.nick,
5450 		                channel: channel,
5451 		                message: message
5452 		            }));
5453 		        },
5454 		        channel: channel
5455 		    };
5456 		};
5457 		this.mediaConnection.onstream = function(stream) {
5458 			open = true;
5459 			if (self.onMediaStream != null) {
5460 				self.onMediaStream(stream);
5461 			}
5462 		};
5463 		this.mediaConnection.onstreamended = function(stream) {
5464 			if (self.onMediaStreamEnded != null) {
5465 				self.onMediaStreamEnded(stream);
5466 			}
5467 		};
5468 		this.mediaConnection.onNewSession = function(session) {
5469 		    session.join({
5470 			    audio: shareAudio,
5471 			    video: shareVideo
5472 			});
5473 		};
5474 		if (this.nick != null) {
5475 			this.mediaConnection.userid = this.nick;
5476 		}
5477 		//connection.log = false;
5478 		this.mediaConnection.onerror = function(error) {
5479 			SDK.error(error);
5480 		}
5481 		this.mediaConnection.onMediaError = function(error) {
5482 			SDK.error(error);
5483 		}
5484 		this.mediaConnection.connect();
5485 	    setTimeout(function() {
5486 	    	if (!open) {
5487 	    		self.mediaConnection.open("room");
5488 	    	}
5489 	    }, 5000);
5490 	}
5491 	
5492 	/**
5493 	 * Disconnect from the active channels media feed (video, audio).
5494 	 */
5495 	this.disconnectMedia = function() {
5496 		if (this.mediaConnection != null) {
5497 			this.mediaConnection.leave();
5498 			this.mediaConnection = null;
5499 		}
5500 	}
5501 	
5502 	/**
5503 	 * Reset the media feed (audio, video).
5504 	 */
5505 	this.resetMedia = function(shareAudio, shareVideo) {
5506 		this.mediaConnection.session = {
5507 		    audio: shareAudio,
5508 		    video: shareVideo
5509 		};
5510 		for (var streamid in this.mediaConnection.localStreams) {
5511 			var stream = this.mediaConnection.streams[streamid];
5512 			if (!shareAudio || !shareVideo) {
5513 				stream.mute({
5514 				    audio: !shareAudio,
5515 				    video: !shareVideo
5516 				});
5517 			}
5518 			if (shareAudio || shareVideo) {
5519 				stream.unmute({
5520 				    audio: shareAudio,
5521 				    video: shareVideo
5522 				});
5523 			}
5524 		}
5525 	}
5526 
5527 	/**
5528 	 * Decrease the size of the video element for the userid.
5529 	 */
5530 	this.shrinkVideo = function(user) {
5531 		var streams = this.mediaConnection.streams.selectAll({remote:true, local:true});
5532 		for (i = 0; i < streams.length; i++) {
5533 			stream = streams[i];
5534 			if (stream.userid == user) {
5535 			    stream.mediaElement.height = stream.mediaElement.height / 1.5;
5536 			}
5537 		}
5538 	};
5539 
5540 	/**
5541 	 * Increase the size of the video element for the userid.
5542 	 */
5543 	this.expandVideo = function(user) {
5544 		var streams = this.mediaConnection.streams.selectAll({remote:true, local:true});
5545 		for (i = 0; i < streams.length; i++) {
5546 			stream = streams[i];
5547 			if (stream.userid == user) {
5548 			    stream.mediaElement.height = stream.mediaElement.height * 1.5;
5549 			}
5550 		}
5551 	};
5552 
5553 	/**
5554 	 * Mute the audio for the userid.
5555 	 */
5556 	this.muteAudio = function(user) {
5557 		var streams = this.mediaConnection.streams.selectAll({remote:true, local:true});
5558 		for (i = 0; i < streams.length; i++) {
5559 			stream = streams[i];
5560 			if (stream.userid == user) {
5561 			    stream.mute({
5562 			        audio: true
5563 			    });
5564 			}
5565 		}
5566 	};
5567 
5568 	/**
5569 	 * Mute the video for the userid.
5570 	 */
5571 	this.muteVideo = function(user) {
5572 		var streams = this.mediaConnection.streams.selectAll({remote:true, local:true});
5573 		for (i = 0; i < streams.length; i++) {
5574 			stream = streams[i];
5575 			if (stream.userid == user) {
5576 			    stream.mute({
5577 			        video: true
5578 			    });
5579 			}
5580 		}			
5581 	};
5582 
5583 	/**
5584 	 * Sent a text message to the channel.
5585 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5586 	 * Note, the listener will receive its own messages.
5587 	 */
5588 	this.sendMessage = function(message) {
5589 		this.checkSocket();
5590 		this.socket.send(message);
5591 	};
5592 
5593 	this.sendAttachment = function(file, resize, form) {
5594 		var self = this;
5595 		var media = new MediaConfig();
5596 		if (this.channel == null) {
5597 			this.listener.error("Missing channel property");
5598 			return false;
5599 		}
5600 		media.instance = this.channel.id;
5601 		media.name = file.name;
5602 		media.type = file.type;
5603 		if (!resize && file.size > SDK.MAX_FILE_UPLOAD) {
5604 			this.listener.error("File exceeds maximum upload size of " + (SDK.MAX_FILE_UPLOAD / 1000000) + "meg");
5605 		} else {
5606 			this.sdk.error = function(message) {
5607 				self.listener.error(message);
5608 			}
5609 			this.sdk.createChannelAttachment(media, file, resize, form, function(media) {
5610 				var message = "file: " + file.name + " : " + file.type + " : " + self.sdk.fetchLink(media.file);
5611 				self.sendMessage(message);
5612 			})
5613 		}
5614 		return false;
5615 	};
5616 
5617 	/**
5618 	 * Accept a private request.
5619 	 * This is also used by an operator to accept the top of the waiting queue.
5620 	 * This can also be used by a user to chat with the channel bot.
5621 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5622 	 */
5623 	this.accept = function() {
5624 		this.checkSocket();
5625 		this.socket.send("accept");
5626 	};
5627 
5628 	/**
5629 	 * Test the connection.
5630 	 * A pong message will be returned, this message will not be broadcast to the channel.
5631 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5632 	 */
5633 	this.ping = function() {
5634 		this.checkSocket();
5635 		this.socket.send("ping");
5636 	};
5637 
5638 	/**
5639 	 * Exit from the current private channel.
5640 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5641 	 */
5642 	this.exit = function() {
5643 		this.checkSocket();
5644 		this.socket.send("exit");
5645 	};
5646 
5647 	/**
5648 	 * Change to spy mode.
5649 	 * This allows admins to monitor the entire channel.
5650 	 */
5651 	this.spyMode = function() {
5652 		this.checkSocket();
5653 		this.socket.send("mode: spy");
5654 	};
5655 
5656 	/**
5657 	 * Change to normal mode.
5658 	 */
5659 	this.normalMode = function() {
5660 		this.checkSocket();
5661 		this.socket.send("mode: normal");
5662 	};
5663 
5664 	/**
5665 	 * Request a private chat session with a user.
5666 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5667 	 */
5668 	this.pvt = function(user) {
5669 		this.checkSocket();
5670 		this.socket.send("pvt: " + user);
5671 	};
5672 
5673 	/**
5674 	 * Boot a user from the channel.
5675 	 * You must be a channel administrator to boot a user.
5676 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5677 	 */
5678 	this.boot = function(user) {
5679 		this.checkSocket();
5680 		this.socket.send("boot: " + user);
5681 	};
5682 
5683 	/**
5684 	 * Send a private message to a user.
5685 	 * This call is asynchronous, any error or success with be sent as a separate message to the listener.
5686 	 */
5687 	this.whisper = function(user, message) {
5688 		this.checkSocket();
5689 		this.socket.send("whisper:" + user + ": " + message);
5690 	};
5691 
5692 	/**
5693 	 * Disconnect from the channel.
5694 	 */
5695 	this.disconnect = function() {
5696     	this.setKeepAlive(false);
5697     	if (this.socket != null) {
5698     		this.socket.disconnect();
5699     	}
5700     	disconnectMedia();
5701 	};
5702 	
5703 	this.checkSocket = function() {
5704 		if (this.socket == null) {
5705 			throw "Not connected";
5706 		}
5707 	};
5708 
5709 	this.toggleKeepAlive = function() {
5710 		this.setKeepAlive(!this.keepAlive);
5711 	}
5712 
5713 	this.setKeepAlive = function(keepAlive) {
5714 		this.keepAlive = keepAlive;
5715 		if (!keepAlive && this.keepAliveInterval != null) {
5716 			clearInterval(this.keepAliveInterval);
5717 		} else if (keepAlive && this.keepAliveInterval == null) {
5718 			this.keepAliveInterval = setInterval(
5719 					function() {
5720 						this.ping()
5721 					},
5722 					600000);
5723 		}
5724 	}
5725 }
5726 
5727 /**
5728 * Connection class for a REST service connection.
5729 * The SDK connection gives you access to the Bot Libre services using a REST web API.
5730 * <p>
5731 * The services include:
5732 * <ul>
5733 * <li> User management (account creation, validation)
5734 * <li> Bot access, chat, and administration
5735 * <li> Forum access, posting, and administration
5736 * <li> Live chat access, chat, and administration
5737 * <li> Script, Graphic, and Domain access, and administration
5738 * </ul>
5739  * @class
5740  * @property user
5741  * @property domain
5742  * @property credentials
5743  * @property debug
5744  * @property error
5745 */
5746 function SDKConnection() {
5747 	this.user;
5748 	this.domain;
5749 	this.credentials = new Credentials();
5750 	this.debug = SDK.debug;
5751 	this.error = SDK.error;
5752 	
5753 	this.exception;
5754 	
5755 	/**
5756 	 * Validate the user credentials (password, or token).
5757 	 * The user details are returned (with a connection token, password removed).
5758 	 * The user credentials are soted in the connection, and used on subsequent calls.
5759 	 * An SDKException is thrown if the connect failed.
5760 	 */
5761 	this.connect = function(config, processor) {
5762 		var self = this;
5763 		this.fetchUser(config, function(user) {
5764 			self.user = user;
5765 			processor(user);
5766 		});
5767 	}
5768 	
5769 	/**
5770 	 * Connect to the live chat channel and return a LiveChatConnection.
5771 	 * A LiveChatConnection is separate from an SDKConnection and uses web sockets for
5772 	 * asynchronous communication.
5773 	 * The listener will be notified of all messages.
5774 	 */
5775 	this.openLiveChat = function(channel, listener) {
5776 		var connection = new LiveChatConnection();
5777 		connection.sdk = this;
5778 		connection.credentials = this.credentials;
5779 		connection.listener = listener;
5780 		connection.connect(channel, this.user);
5781 		return connection;
5782 	}
5783 	
5784 	/**
5785 	 * Connect to the domain.
5786 	 * A domain is an isolated content space.
5787 	 * Any browse or query request will be specific to the domain's content.
5788 	 */	
5789 	this.switchDomain = function(config, processor) {
5790 		var self = this;
5791 		this.fetch(config, function(domain) {
5792 			self.domain = domain;
5793 			processor(domain);
5794 		});
5795 	}
5796 	
5797 	/**
5798 	 * Disconnect from the connection.
5799 	 * An SDKConnection does not keep a live connection, but this resets its connected user and domain.
5800 	 */	
5801 	this.disconnect = function() {
5802 		this.user = null;
5803 		this.domain = null;
5804 	}
5805 	
5806 	/**
5807 	 * Fetch the user details for the user credentials.
5808 	 * A token or password is required to validate the user.
5809 	 */	
5810 	this.fetchUser = function(config, processor) {
5811 		config.addCredentials(this);
5812 		this.POST(this.credentials.rest + "/check-user", config.toXML(), function(xml) {
5813 			if (xml == null) {
5814 				return;
5815 			}
5816 			var user = new UserConfig();
5817 			user.parseXML(xml);
5818 			processor(user);
5819 		});
5820 	}
5821 	
5822 	/**
5823 	 * Fetch the user details for the user id.
5824 	 */	
5825 	this.viewUser = function(config, processor) {
5826 		config.addCredentials(this);
5827 		this.POST(this.credentials.rest + "/view-user", config.toXML(), function(xml) {
5828 			if (xml == null) {
5829 				return;
5830 			}
5831 			var user = new UserConfig();
5832 			user.parseXML(xml);
5833 			processor(user);
5834 		});
5835 	}
5836 	
5837 	/**
5838 	 * Fetch the URL for the image from the server.
5839 	 */	
5840 	this.fetchImage = function(image) {
5841 		return this.credentials.url + "/" + image;
5842 	}
5843 	
5844 	/**
5845 	 * Fetch the URL for the image from the server.
5846 	 */	
5847 	this.fetchLink = function(image) {
5848 		return this.credentials.url + "/" + image;
5849 	}
5850 	
5851 	/**
5852 	 * Fetch the forum post details for the forum post id.
5853 	 */	
5854 	this.fetchForumPost = function(config, processor) {
5855 		config.addCredentials(this);
5856 		this.POST(this.credentials.rest + "/check-forum-post", config.toXML(), function(xml) {
5857 			if (xml == null) {
5858 				return;
5859 			}
5860 			var post = new ForumPostConfig();
5861 			post.parseXML(xml);
5862 			processor(post);
5863 		});
5864 	}
5865 	
5866 	/**
5867 	 * Create a new user.
5868 	 */
5869 	this.createUser = function(config, processor) {
5870 		config.addCredentials(this);
5871 		this.POST(this.credentials.rest + "/create-user", config.toXML(), function(xml) {
5872 			if (xml == null) {
5873 				return null;
5874 			}
5875 			var user = new UserConfig();
5876 			user.parseXML(xml);
5877 			this.user = user;
5878 			processor(user);
5879 		});
5880 	}
5881 
5882 	/**
5883 	 * Create a new file/image/media attachment for a chat channel.
5884 	 */
5885 	this.createChannelAttachment = function(config, file, resize, form, processor) {
5886 		config.addCredentials(this);
5887 		if (resize) {
5888 			this.POST_IMAGE(this.credentials.rest + "/create-channel-attachment", file, form, config.toXML(), function(xml) {
5889 				if (xml == null) {
5890 					return null;
5891 				}
5892 				var media = new MediaConfig();
5893 				media.parseXML(xml);
5894 				processor(media);
5895 			});
5896 		} else {
5897 			this.POST_FILE(this.credentials.rest + "/create-channel-attachment", form, config.toXML(), function(xml) {
5898 				if (xml == null) {
5899 					return null;
5900 				}
5901 				var media = new MediaConfig();
5902 				media.parseXML(xml);
5903 				processor(media);
5904 			});
5905 		}
5906 	}
5907 
5908 	/**
5909 	 * Create a new file/image/media attachment for a bot.
5910 	 */
5911 	this.createBotAttachment = function(config, file, resize, form, processor) {
5912 		config.addCredentials(this);
5913 		if (resize) {
5914 			this.POST_IMAGE(this.credentials.rest + "/create-bot-attachment", file, form, config.toXML(), function(xml) {
5915 				if (xml == null) {
5916 					return null;
5917 				}
5918 				var media = new MediaConfig();
5919 				media.parseXML(xml);
5920 				processor(media);
5921 			});
5922 		} else {
5923 			this.POST_FILE(this.credentials.rest + "/create-bot-attachment", form, config.toXML(), function(xml) {
5924 				if (xml == null) {
5925 					return null;
5926 				}
5927 				var media = new MediaConfig();
5928 				media.parseXML(xml);
5929 				processor(media);
5930 			});
5931 		}
5932 	}
5933 
5934 	/**
5935 	 * Create a new file/image/media attachment for a forum.
5936 	 */
5937 	this.createForumAttachment = function(config, file, resize, form, processor) {
5938 		config.addCredentials(this);
5939 		if (resize) {
5940 			this.POST_IMAGE(this.credentials.rest + "/create-forum-attachment", file, form, config.toXML(), function(xml) {
5941 				if (xml == null) {
5942 					return null;
5943 				}
5944 				var media = new MediaConfig();
5945 				media.parseXML(xml);
5946 				processor(media);
5947 			});
5948 		} else {
5949 			this.POST_FILE(this.credentials.rest + "/create-forum-attachment", form, config.toXML(), function(xml) {
5950 				if (xml == null) {
5951 					return null;
5952 				}
5953 				var media = new MediaConfig();
5954 				media.parseXML(xml);
5955 				processor(media);
5956 			});
5957 		}
5958 	}
5959 
5960 	/**
5961 	 * Create a new file/image/media attachment for a forum and insert the http link into the textarea.
5962 	 */
5963 	this.uploadForumAttachment = function(forum, resize, processor) {
5964 		if (!(window.File && window.FileReader && window.FileList && window.Blob)) {
5965 			this.error('The File APIs are not fully supported in this browser.');
5966 			return false;
5967 		}
5968 		var form = document.createElement("form");
5969 		form.enctype = "multipart/form-data";
5970 		form.method = "post";
5971 		form.name = "fileinfo";
5972 		var fileInput = document.createElement("input");
5973 		var self = this;
5974 		fileInput.name = "file";
5975 		fileInput.type = "file";
5976 		form.appendChild(fileInput);
5977 		fileInput.onchange = function() {
5978 			var file = fileInput.files[0];
5979 			self.uploadForumFile(file, forum, resize, form, processor);
5980 		}
5981 		fileInput.click();
5982 	};
5983 
5984 	/**
5985 	 * Create a new file/image/media attachment for a forum.
5986 	 */
5987 	this.uploadForumFile = function(file, forum, resize, form, processor) {
5988 		var self = this;
5989 		var media = new MediaConfig();
5990 		media.instance = forum;
5991 		media.name = file.name;
5992 		media.type = file.type;
5993 		if (!resize && file.size > SDK.MAX_FILE_UPLOAD) {
5994 			this.error("File exceeds maximum upload size of " + (SDK.MAX_FILE_UPLOAD / 1000000) + "meg");
5995 		} else {
5996 			this.createForumAttachment(media, file, resize, form, function(media) {
5997 				var link = self.fetchLink(media.file);
5998 				if (processor != null) {
5999 					processor(link, file.name);
6000 				}
6001 			})
6002 		}
6003 	};
6004 	
6005 	/**
6006 	 * Create a new forum post.
6007 	 * You must set the forum id for the post.
6008 	 */
6009 	this.createForumPost = function(config, processor) {
6010 		config.addCredentials(this);
6011 		var xml = POST(this.credentials.rest + "/create-forum-post", config.toXML(), function(xml) {
6012 			if (xml == null) {
6013 				return null;
6014 			}
6015 			var post = new ForumPostConfig();
6016 			post.parseXML(xml);
6017 			processor(post);
6018 		});
6019 	}
6020 	
6021 	/**
6022 	 * Create a reply to a forum post.
6023 	 * You must set the parent id for the post replying to.
6024 	 */
6025 	this.createReply = function(config, processor) {
6026 		config.addCredentials(this);
6027 		var xml = POST(this.credentials.rest + "/create-reply", config.toXML(), function(xml) {
6028 			if (xml == null) {
6029 				return null;
6030 			}
6031 			var reply = new ForumPostConfig();
6032 			reply.parseXML(xml);
6033 			processor(reply);			
6034 		});
6035 	}
6036 	
6037 	/**
6038 	 * Fetch the content details from the server.
6039 	 * The id or name and domain of the object must be set.
6040 	 */
6041 	this.fetch = function(config, processor) {
6042 		config.addCredentials(this);
6043 		this.POST(this.credentials.rest + "/check-" + config.type, config.toXML(), function(xml) {
6044 			if (xml == null) {
6045 				return null;
6046 			}
6047 			var config2 = new config.constructor();
6048 			config2.parseXML(xml);
6049 			processor(config2)
6050 		});
6051 	}
6052 	
6053 	/**
6054 	 * Update the forum post.
6055 	 */
6056 	this.updateForumPost = function(config, processor) {
6057 		config.addCredentials(this);
6058 		this.POST(this.credentials.rest + "/update-forum-post", config.toXML(), function(xml) {
6059 			if (xml == null) {
6060 				return null;
6061 			}
6062 			var config = new ForumPostConfig();
6063 			config.parseXML(xml);
6064 			processor(config);			
6065 		});
6066 	}
6067 	
6068 	/**
6069 	 * Update the user details.
6070 	 * The password must be passed to allow the update.
6071 	 */
6072 	this.updateUser = function(config) {
6073 		config.addCredentials(this);
6074 		this.POST(this.credentials.rest + "/update-user", config.toXML(), function(xml) {
6075 			return;
6076 		});
6077 	}
6078 	
6079 	/**
6080 	 * Permanently delete the forum post with the id.
6081 	 */
6082 	this.deleteForumPost = function(config) {
6083 		config.addCredentials(this);
6084 		this.POST(this.credentials.rest + "/delete-forum-post", config.toXML(), function(xml) {
6085 			return;
6086 		});
6087 	}
6088 	
6089 	/**
6090 	 * Flag the content as offensive, a reason is required.
6091 	 */
6092 	this.flag = function(config) {
6093 		config.addCredentials(this);
6094 		this.POST(this.credentials.rest + "/flag-" + config.getType(), config.toXML(), function(xml) {
6095 			return;
6096 		});
6097 	}
6098 	
6099 	/**
6100 	 * Flag the forum post as offensive, a reason is required.
6101 	 */
6102 	this.flagForumPost = function(config) {
6103 		config.addCredentials(this);
6104 		this.POST(this.credentials.rest + "/flag-forum-post", config.toXML(), function(xml) {
6105 			return;
6106 		});
6107 	}
6108 	
6109 	/**
6110 	 * Flag the user post as offensive, a reason is required.
6111 	 */
6112 	this.flagUser = function(config) {
6113 		config.addCredentials(this);
6114 		this.POST(this.credentials.rest + "/flag-user", config.toXML(), function(xml) {
6115 			return;
6116 		});
6117 	}
6118 	
6119 	/**
6120 	 * Process the bot chat message and return the bot's response.
6121 	 * The ChatConfig should contain the conversation id if part of a conversation.
6122 	 * If a new conversation the conversation id is returned in the response. 
6123 	 */
6124 	this.chat = function(config, processor) {
6125 		config.addCredentials(this);
6126 		this.POST(this.credentials.rest + "/chat", config.toXML(), function(xml) {
6127 			if (xml == null) {
6128 				return null;
6129 			}
6130 			var responseMessage = new ChatResponse();
6131 			responseMessage.parseXML(xml);
6132 			processor(responseMessage);			
6133 		});
6134 	}
6135 	
6136 	/**
6137 	 * Process the bot command message and return the bot's response.
6138 	 * The CommandConfig should contain the conversation id if part of a conversation.
6139 	 * If a new conversation the conversation id is returned in the response. 
6140 	 */
6141 	this.command = function(config, processor) {
6142 		config.addCredentials(this);
6143 		this.POST(this.credentials.rest + "/command", config.toXML(), function(xml) {
6144 			if (xml == null) {
6145 				return null;
6146 			}
6147 			var responseMessage = new ChatResponse();
6148 			responseMessage.parseXML(xml);
6149 			processor(responseMessage);			
6150 		});
6151 	}
6152 	
6153 	/**
6154 	 * Process the avatar message and return the avatar's response.
6155 	 */
6156 	this.avatarMessage = function(config, processor) {
6157 		config.addCredentials(this);
6158 		this.POST(this.credentials.rest + "/avatar-message", config.toXML(), function(xml) {
6159 			if (xml == null) {
6160 				return null;
6161 			}
6162 			var responseMessage = new ChatResponse();
6163 			responseMessage.parseXML(xml);
6164 			processor(responseMessage);			
6165 		});
6166 	}
6167 	
6168 	/**
6169 	 * Return the list of user details for the comma separated values list of user ids.
6170 	 */
6171 	this.fetchAllUsers = function(usersCSV, processor) {
6172 		var config = new UserConfig();
6173 		config.user = usersCSV;
6174 		config.addCredentials(this);
6175 		this.POST(this.credentials.rest + "/get-users", config.toXML(), function(xml) {
6176 			var users = [];
6177 			if (xml != null) {
6178 				for (var index = 0; index < xml.childNodes.length; index++) {
6179 					var child = xml.childNodes[index];
6180 					var userConfig = new UserConfig();
6181 					userConfig.parseXML(child);
6182 					users[user.length] = userConfig;
6183 				}
6184 			}
6185 			processor(users);
6186 		});
6187 	}
6188 	
6189 	/**
6190 	 * Return the list of forum posts for the forum browse criteria.
6191 	 */
6192 	this.fetchPosts = function(config, processor) {
6193 		config.addCredentials(this);
6194 		var xml = this.POST(this.credentials.rest + "/get-forum-posts", config.toXML(), function(xml) {
6195 			var instances = [];
6196 			if (xml != null) {
6197 				for (var index = 0; index < xml.childNodes.length; index++) {
6198 					var child = xml.childNodes[index];
6199 					var post = new ForumPostConfig();
6200 					post.parseXML(child);
6201 					instances[instances.length] = post;
6202 				}
6203 			}
6204 			processor(instances);
6205 		});
6206 	}
6207 	
6208 	/**
6209 	 * Return the list of categories for the type, and domain.
6210 	 */
6211 	this.fetchCategories = function(config, processor) {
6212 		config.addCredentials(this);
6213 		var xml = this.POST(this.credentials.rest + "/get-categories", config.toXML(), function(xml) {
6214 			var categories = [];
6215 			categories[0] = "";
6216 			if (xml != null) {
6217 				for (var index = 0; index < xml.childNodes.length; index++) {
6218 					categories[categories.length] = (xml.childNodes[index].getAttribute("name"));
6219 				}
6220 			}
6221 			processor(categories);
6222 		});
6223 	}
6224 	
6225 	/**
6226 	 * Return the list of tags for the type, and domain.
6227 	 */
6228 	this.fetchTags = function(config, processor) {
6229 		config.addCredentials(this);
6230 		var xml = this.POST(this.credentials.rest + "/get-tags", config.toXML(), function(xml) {
6231 			var tags = [];
6232 			tags[0] = "";
6233 			if (xml != null) {
6234 				for (var index = 0; index < xml.childNodes.length; index++) {
6235 					tags[tags.length] = (xml.childNodes[index].getAttribute("name"));
6236 				}
6237 			}
6238 			processor(tags);			
6239 		});
6240 	}
6241 	
6242 	/**
6243 	 * Return the users for the content.
6244 	 */
6245 	this.fetchUsers = function(config, processor) {
6246 		config.addCredentials(this);
6247 		var xml = this.POST(this.credentials.rest + "/get-" + config.getType() + "-users", config.toXML(), function(xml) {
6248 			var users = [];
6249 			if (xml != null) {
6250 				for (var index = 0; index < xml.childNodes.length; index++) {
6251 					var user = new UserConfig();
6252 					user.parseXML(xml.childNodes[index]);
6253 					users[users.length] = (user.user);
6254 				}
6255 			}
6256 			processor(users);
6257 			
6258 		});
6259 	}
6260 	
6261 	/**
6262 	 * Initialize the bot's avatar for a chat session.
6263 	 * This can be done before processing the first message for a quick response.
6264 	 * @deprecated replaced by initChat()
6265 	 */
6266 	this.initAvatar = function(config, processor) {
6267 		config.addCredentials(this);
6268 		this.POST(this.credentials.rest + "/init-avatar", config.toXML(), function(xml) {
6269 			if (xml == null) {
6270 				return;
6271 			}
6272 			var response = new ChatResponse();
6273 			response.parseXML(xml);
6274 			processor(response);
6275 		});
6276 	}
6277 	
6278 	/**
6279 	 * Initialize the bot's avatar for a chat session.
6280 	 * This can be done before processing the first message for a quick response.
6281 	 */
6282 	this.initChat = function(config, processor) {
6283 		config.addCredentials(this);
6284 		this.POST(this.credentials.rest + "/init-chat", config.toXML(), function(xml) {
6285 			if (xml == null) {
6286 				return;
6287 			}
6288 			var response = new ChatResponse();
6289 			response.parseXML(xml);
6290 			processor(response);
6291 		});
6292 	}
6293 	
6294 	/**
6295 	 * Return the conversation's chat settings.
6296 	 */
6297 	this.chatSettings = function(config, processor) {
6298 		config.addCredentials(this);
6299 		this.POST(this.credentials.rest + "/chat-settings", config.toXML(), function(xml) {
6300 			if (xml == null) {
6301 				return;
6302 			}
6303 			var settings = new ChatSettings();
6304 			settings.parseXML(xml);
6305 			processor(settings);
6306 		});
6307 	}
6308 	
6309 	/**
6310 	 * Return the bot's voice configuration.
6311 	 */
6312 	this.trainInstance = function(config) {
6313 		config.addCredentials(this);
6314 		this.POST(this.credentials.rest + "/train-instance", config.toXML(), function(xml) {
6315 			return;
6316 		});
6317 	}
6318 	
6319 	/**
6320 	 * Return the bot's voice configuration.
6321 	 */
6322 	this.fetchVoice = function(config, processor) {
6323 		config.addCredentials(this);
6324 		this.POST(this.credentials.rest + "/get-voice", config.toXML(), function(xml) {
6325 			if (xml == null) {
6326 				return;
6327 			}
6328 			var voice = new VoiceConfig();
6329 			voice.parseXML(xml);
6330 			processor(voice);
6331 		});
6332 	}
6333 	
6334 	/**
6335 	 * Return the list of content for the browse criteria.
6336 	 * The type defines the content type (one of Bot, Forum, Channel, Domain).
6337 	 */
6338 	this.browse = function(config, processor) {
6339 		config.addCredentials(this);
6340 		var type = "";
6341 		if (config.type == "Bot") {
6342 			type = "/get-instances";
6343 		} else if (config.type == "Forum") {
6344 			type = "/get-forums";
6345 		} else if (config.type == "Channel") {
6346 			type = "/get-channels";
6347 		} else if (config.type == "Domain") {
6348 			type = "/get-domains";
6349 		} else if (config.type == "Graphic") {
6350 			type = "/get-graphics";
6351 		} else if (config.type == "Avatar") {
6352 			type = "/get-avatars";
6353 		} else if (config.type == "Script") {
6354 			type = "/get-scripts";
6355 		}
6356 		this.POST(this.credentials.rest + type, config.toXML(), function(xml) {
6357 			var instances = [];
6358 			if (xml != null) {
6359 				for (var index = 0; index < xml.childNodes.length; index++) {
6360 					var instance = null;
6361 					if (config.type == "Bot") {
6362 						instance = new InstanceConfig();
6363 					} else if (config.type == "Forum") {
6364 						instance = new ForumConfig();
6365 					} else if (config.type == "Channel") {
6366 						instance = new ChannelConfig();
6367 					} else if (config.type == "Domain") {
6368 						instance = new DomainConfig();
6369 					} else if (config.type == "Avatar") {
6370 						instance = new AvatarConfig();
6371 					} else if (config.type == "Script") {
6372 						instance = new ScriptConfig();
6373 					} else if (config.type == "Graphic") {
6374 						instance = new GraphicConfig();
6375 					}
6376 					instance.parseXML(xml.childNodes[index]);
6377 					instances[instances.length] = (instance);
6378 				}
6379 			}
6380 			processor(instances);
6381 		});
6382 	}
6383 
6384 	this.GET = function(url, processor) {	
6385 		if (this.debug) {
6386 			console.log("GET: " + url);
6387 		}
6388 		var xml = null;
6389 		var request = new XMLHttpRequest();
6390 		var debug = this.debug;
6391 		var self = this;
6392 		request.onreadystatechange = function() {
6393 			if (request.readyState != 4) return;
6394 			if (request.status != 200) {
6395 				console.log('Error: SDK GET web request failed');
6396 				if (debug) {
6397 					console.log(request.statusText);
6398 					console.log(request.responseText);
6399 					console.log(request.responseXML);
6400 				}
6401 				if (request.statusText != null && request.responseText != null && request.responseText.indexOf("<html>") != -1) {
6402 					self.error(request.statusText);					
6403 				} else {
6404 					self.error(request.responseText);
6405 				}
6406 				return;
6407 			}
6408 			processor(request.responseXML.childNodes[0]);
6409 		}
6410 		
6411 		request.open('GET', url, true);
6412 		request.send();
6413 	}
6414 
6415 	this.POST = function(url, xml, processor) {
6416 		if (this.debug) {
6417 			console.log("POST: " + url);
6418 			console.log("XML: " + xml);
6419 		}
6420 		var request = new XMLHttpRequest();
6421 		var debug = this.debug;
6422 		var self = this;
6423 		request.onreadystatechange = function() {
6424 			if (debug) {
6425 				console.log(request.readyState);
6426 				console.log(request.status);
6427 				console.log(request.statusText);
6428 				console.log(request.responseText);
6429 				console.log(request.responseXML);
6430 			}
6431 			if (request.readyState != 4) return;
6432 			if (request.status != 200 && request.status != 204) {
6433 				console.log('Error: SDK POST web request failed');
6434 				if (debug) {
6435 					console.log(request.statusText);
6436 					console.log(request.responseText);
6437 					console.log(request.responseXML);
6438 				}
6439 				if (request.statusText != null && request.responseText != null && request.responseText.indexOf("<html>") != -1) {
6440 					self.error(request.statusText);					
6441 				} else {
6442 					self.error(request.responseText);
6443 				}
6444 				return;
6445 			}
6446 			processor(request.responseXML.childNodes[0]);
6447 		};
6448 		
6449 		request.open('POST', url, true);
6450 		request.setRequestHeader("Content-Type", "application/xml");
6451 		request.send(xml);
6452 	}
6453 	
6454 	this.POST_FILE = function(url, form, xml, processor) {
6455 		if (this.debug) {
6456 			console.log("POST FILE: " + url);
6457 			console.log("FORM: " + form);
6458 			console.log("XML: " + xml);
6459 		}
6460 		var request = new XMLHttpRequest();
6461 		var formData = new FormData(form);
6462 		formData.append("xml", xml);
6463 		var debug = this.debug;
6464 		var self = this;
6465 		request.onreadystatechange = function() {
6466 			if (debug) {
6467 				console.log(request.readyState);
6468 				console.log(request.status);
6469 				console.log(request.statusText);
6470 				console.log(request.responseText);
6471 				console.log(request.responseXML);
6472 			}
6473 			if (request.readyState != 4) return;
6474 			if (request.status != 200 && request.status != 204) {
6475 				console.log('Error: SDK POST web request failed');
6476 				if (debug) {
6477 					console.log(request.statusText);
6478 					console.log(request.responseText);
6479 					console.log(request.responseXML);
6480 				}
6481 				if (request.statusText != null && request.responseText != null && request.responseText.indexOf("<html>") != -1) {
6482 					self.error(request.statusText);					
6483 				} else {
6484 					self.error(request.responseText);
6485 				}
6486 				return;
6487 			}
6488 			processor(request.responseXML.childNodes[0]);
6489 		};
6490 		
6491 		request.open('POST', url, true);
6492 		//request.setRequestHeader("Content-Type", "multipart/form-data");
6493 		request.send(formData);
6494 	}
6495 	
6496 	this.POST_IMAGE = function(url, file, form, xml, processor) {
6497 		var self = this;
6498 		var debug = this.debug;
6499 		var reader = new FileReader();
6500 		reader.onloadend = function() {
6501 			var tempImg = new Image();
6502 			tempImg.src = reader.result;
6503 			tempImg.onload = function() {
6504 				var MAX_WIDTH = 300;
6505 				var MAX_HEIGHT = 300;
6506 				var tempW = tempImg.width;
6507 				var tempH = tempImg.height;
6508 				if (tempW > tempH) {
6509 					if (tempW > MAX_WIDTH) {
6510 						 tempH *= MAX_WIDTH / tempW;
6511 						 tempW = MAX_WIDTH;
6512 					}
6513 				} else {
6514 					if (tempH > MAX_HEIGHT) {
6515 						 tempW *= MAX_HEIGHT / tempH;
6516 						 tempH = MAX_HEIGHT;
6517 					}
6518 				}
6519 				var canvas = document.createElement('canvas');
6520 				canvas.width = tempW;
6521 				canvas.height = tempH;
6522 				var ctx = canvas.getContext("2d");
6523 				ctx.fillStyle = '#fff';
6524 				ctx.fillRect(0, 0, canvas.width, canvas.height);				
6525 				ctx.drawImage(this, 0, 0, tempW, tempH);
6526 	            var dataUrl = canvas.toDataURL('image/jpeg');
6527 	            var blob = SDK.dataURLToBlob(dataUrl);
6528 
6529 				var request = new XMLHttpRequest();
6530 				var formData = new FormData();
6531 				formData.append("xml", xml);
6532 				formData.append('file', blob, file.name);
6533 				request.onreadystatechange = function() {
6534 					if (debug) {
6535 						console.log(request.readyState);
6536 						console.log(request.status);
6537 						console.log(request.statusText);
6538 						console.log(request.responseText);
6539 						console.log(request.responseXML);
6540 					}
6541 					if (request.readyState != 4) return;
6542 					if (request.status != 200 && request.status != 204) {
6543 						console.log('Error: SDK POST web request failed');
6544 						if (debug) {
6545 							console.log(request.statusText);
6546 							console.log(request.responseText);
6547 							console.log(request.responseXML);
6548 						}
6549 						if (request.statusText != null && request.responseText != null && request.responseText.indexOf("<html>") != -1) {
6550 							self.error(request.statusText);					
6551 						} else {
6552 							self.error(request.responseText);
6553 						}
6554 						return;
6555 					}
6556 					processor(request.responseXML.childNodes[0]);
6557 				};
6558 				
6559 				request.open('POST', url, true);
6560 				//request.setRequestHeader("Content-Type", "multipart/form-data");
6561 				request.send(formData);
6562 			}
6563 		 }
6564 		 reader.readAsDataURL(file);
6565 	}
6566 }
6567 
6568 /**
6569  * Abstract root class for all web API message objects.
6570  * Defines the required application id, and common fields.
6571  * @class
6572  * @property application
6573  * @property domain
6574  * @property user
6575  * @property token
6576  * @property instance
6577  * @property type
6578  */
6579 function Config() {
6580 	/** The application ID.  This is require to authenticate the API usage.  You can obtain your application ID from your user page. */
6581 	this.application;
6582 	/** Optional domain id, if object is not on the server's default domain. */
6583 	this.domain;
6584 	/** User ID, required for content creation, secure content access, or to identify the user. */
6585 	this.user;
6586 	/** User's access token, returned from connect web API, can be used in place of password in subsequent calls, and stored in a cookie.   The user's password should never be stored. */
6587 	this.token;
6588 	/** The id or name of the bot or content instance to access. */
6589 	this.instance;
6590 	/** Type of instance to access, ("Bot", "Forum", "Channel", "Domain") */
6591 	this.type;
6592 	
6593 	this.addCredentials = function(connection) {
6594 		this.application = connection.credentials.applicationId;
6595 		if (connection.user != null) {
6596 			this.user = connection.user.user;
6597 			this.token = connection.user.token;
6598 		}
6599 		if (connection.domain != null) {
6600 			this.domain = connection.domain.id;
6601 		}
6602 	}
6603 	
6604 	this.writeCredentials = function(xml) {
6605 		if (this.user != null && this.user.length > 0) {
6606 			xml = xml + (" user=\"" + this.user + "\"");
6607 		}
6608 		if (this.token != null && this.token.length > 0) {
6609 			xml = xml + (" token=\"" + this.token + "\"");
6610 		}
6611 		if (this.type != null && this.type.length > 0) {
6612 			xml = xml + (" type=\"" + this.type + "\"");
6613 		}
6614 		if (this.instance != null && this.instance.length > 0) {
6615 			xml = xml + (" instance=\"" + this.instance + "\"");
6616 		}
6617 		if (this.application != null && this.application.length > 0) {
6618 			xml = xml + (" application=\"" + this.application + "\"");
6619 		}
6620 		if (this.domain != null && this.domain.length > 0) {
6621 			xml = xml + (" domain=\"" + this.domain + "\"");
6622 		}
6623 		return xml;
6624 	}
6625 }
6626 
6627 /**
6628  * This object models a user.
6629  * It can be used from a chat UI, or with the Libre Web API.
6630  * It can convert itself to/from XML for web API usage.
6631  * This can be used to connect, create, edit, or browse a user instance.
6632  * @class
6633  * @property password
6634  * @property newPassword
6635  * @property hint
6636  * @property name
6637  * @property showName
6638  * @property email
6639  * @property website
6640  * @property bio
6641  * @property over18
6642  * @property avatar
6643  * @property connects
6644  * @property bots
6645  * @property posts
6646  * @property messages
6647  * @property joined
6648  * @property lastConnect
6649  */
6650 function UserConfig() {
6651 	/** Password, require to connect a user, or create a user. */
6652 	this.password;
6653 	/** New password for editting a user's password (password is old password). */
6654 	this.newPassword;
6655 	/** Optional password hint, in case password is forgotten. */
6656 	this.hint;
6657 	/** Optional real name of the user. */
6658 	this.name;
6659 	/** The real name can be hidden from other users. */
6660 	this.showName;
6661 	/** Email, required for message notification, and to reset password. */
6662 	this.email;
6663 	/** Optional user's website. */
6664 	this.website;
6665 	/** Optional user's bio. */
6666 	this.bio;
6667 	this.over18;
6668 	/** Read-only, server local URL for user's avatar image. */
6669 	this.avatar;
6670 	this.avatarThumb;
6671 
6672 	/** Read-only, total user connects. */
6673 	this.connects;
6674 	/** Read-only, total bots created. */
6675 	this.bots;
6676 	/** Read-only, total forum posts. */
6677 	this.posts;
6678 	/** Read-only, total chat messages. */
6679 	this.messages;
6680 	/** Read-only, date user joined. */
6681 	this.joined;
6682 	/** Read-only, date of user's last connect. */
6683 	this.lastConnect;
6684 	
6685 	this.addCredentials = function(connection) {
6686 		this.application = connection.credentials.applicationId;
6687 		if (connection.domain != null) {
6688 			this.domain = connection.domain.id;
6689 		}
6690 	}
6691 
6692 	this.parseXML = function(element) {
6693 		this.user = element.getAttribute("user");
6694 		this.name = element.getAttribute("name");
6695 		this.showName = element.getAttribute("showName");
6696 		this.token = element.getAttribute("token");
6697 		this.email = element.getAttribute("email");
6698 		this.hint = element.getAttribute("hint");
6699 		this.website = element.getAttribute("website");
6700 		this.connects = element.getAttribute("connects");
6701 		this.bots = element.getAttribute("bots");
6702 		this.posts = element.getAttribute("posts");
6703 		this.messages = element.getAttribute("messages");
6704 		this.joined = element.getAttribute("joined");
6705 		this.lastConnect = element.getAttribute("lastConnect");
6706 		
6707 		var node = element.getElementsByTagName("bio")[0];
6708 		if (node != null) {
6709 			this.bio = SDK.innerHTML(node);
6710 		}
6711 		node = element.getElementsByTagName("avatar")[0];
6712 		if (node != null) {
6713 			this.avatar = SDK.innerHTML(node);
6714 		}
6715 	}
6716 	
6717 	this.toXML = function() {
6718 		var xml = "<user";
6719 		xml = this.writeCredentials(xml);
6720 		if (this.password != null) {
6721 			xml = xml + (" password=\"" + this.password + "\"");
6722 		}
6723 		if (this.newPassword != null) {
6724 			xml = xml + (" newPassword=\"" + this.newPassword + "\"");
6725 		}
6726 		if (this.hint != null) {
6727 			xml = xml + (" hint=\"" + this.hint + "\"");
6728 		}
6729 		if (this.name != null) {
6730 			xml = xml + (" name=\"" + this.name + "\"");
6731 		}
6732 		if (this.showName) {
6733 			xml = xml + (" showName=\"" + this.showName + "\"");
6734 		}
6735 		if (this.email != null) {
6736 			xml = xml + (" email=\"" + this.email + "\"");
6737 		}
6738 		if (this.website != null) {
6739 			xml = xml + (" website=\"" + this.website + "\"");
6740 		}
6741 		if (this.over18) {
6742 			xml = xml + (" over18=\"" + this.over18 + "\"");
6743 		}
6744 		xml = xml + (">");
6745 		
6746 		if (this.bio != null) {
6747 			xml = xml + ("<bio>");
6748 			xml = xml + (SDK.escapeHTML(this.bio));
6749 			xml = xml + ("</bio>");
6750 		}
6751 		xml = xml + ("</user>");
6752 		return xml;
6753 	}
6754 		
6755 }
6756 UserConfig.prototype = new Config();
6757 UserConfig.prototype.constructor = UserConfig;
6758 UserConfig.constructor = UserConfig;
6759 
6760 /**
6761  * This object models a chat message sent to a chat bot instance.
6762  * It can be used from a chat UI, or with the Libre Web API.
6763  * It can convert itself to XML for web API usage.
6764  * @class
6765  * @property conversation
6766  * @property speak
6767  * @property correction
6768  * @property offensive
6769  * @property disconnect
6770  * @property emote
6771  * @property action
6772  * @property message
6773  * @property debug
6774  * @property debugLevel
6775  * @property learn
6776  */
6777 function ChatConfig() {
6778 	/** The conversation id for the message.  This will be returned from the first response, and must be used for all subsequent messages to maintain the conversational state.  Without the conversation id, the bot has no context for the reply. */
6779 	this.conversation;
6780 	/** Sets if the voice audio should be generated for the bot's response. */
6781 	this.speak;
6782 	/** Sets the message to be a correction to the bot's last response. */
6783 	this.correction;
6784 	/** Flags the bot's last response as offensive. */
6785 	this.offensive;
6786 	/** Ends the conversation. Conversation should be terminated to converse server resources.  The message can be blank. */
6787 	this.disconnect;
6788 	/** 
6789 	 * Attaches an emotion to the user's message, one of:
6790 	 *  NONE,
6791 	 *  LOVE, LIKE, DISLIKE, HATE,
6792 	 *	RAGE, ANGER, CALM, SERENE,
6793 	 *	ECSTATIC, HAPPY, SAD, CRYING,
6794 	 *	PANIC, AFRAID, CONFIDENT, COURAGEOUS,
6795 	 *	SURPRISE, BORED,
6796 	 *	LAUGHTER, SERIOUS
6797 	 */
6798 	this.emote;
6799 	/** Attaches an action to the user's messages, such as "laugh", "smile", "kiss". */
6800 	this.action;
6801 	/** The user's message text. */
6802 	this.message;
6803 	/** Include the message debug log in the response. */
6804 	this.debug;
6805 	/** Set the debug level, one of: SEVER, WARNING, INFO, CONFIG, FINE, FINER. */
6806 	this.debugLevel;
6807 	/** Enable or disable the bot's learning for this message. */
6808 	this.learn;
6809 	/** Escape and filter the response message HTML content for XSS security. */
6810 	this.secure = SDK.secure;
6811 	/** Strip any HTML tags from the response message. */
6812 	this.plainText;
6813 	/** Send extra info with the message, such as the user's contact info (name email phone). */
6814 	this.info;
6815 	/** Request a specific avatar (by ID). */
6816 	this.avatar;
6817 	/** Request the response avatar media in HD. */
6818 	this.avatarHD = SDK.hd;
6819 	/** Request the response avatar media in a video or image format. */
6820 	this.avatarFormat = SDK.format;
6821 	/** Translate between the user's language and the bot's language. */
6822 	this.language;
6823 	
6824 	this.toXML = function() {
6825 		var xml = "<chat";
6826 		xml = this.writeCredentials(xml);
6827 		if (this.conversation != null) {
6828 			xml = xml + (" conversation=\"" + this.conversation + "\"");
6829 		}
6830 		if (this.emote != null) {
6831 			xml = xml + (" emote=\"" + this.emote + "\"");
6832 		}
6833 		if (this.action != null) {
6834 			xml = xml + (" action=\"" + this.action + "\"");
6835 		}
6836 		if (this.speak) {
6837 			xml = xml + (" speak=\"" + this.speak + "\"");
6838 		}
6839 		if (this.language != null) {
6840 			xml = xml + (" language=\"" + this.language + "\"");
6841 		}
6842 		if (this.avatarHD) {
6843 			xml = xml + (" avatarHD=\"" + this.avatarHD + "\"");
6844 		}
6845 		if (this.avatarFormat != null) {
6846 			xml = xml + (" avatarFormat=\"" + this.avatarFormat + "\"");
6847 		}
6848 		if (this.correction) {
6849 			xml = xml + (" correction=\"" + this.correction + "\"");
6850 		}
6851 		if (this.offensive) {
6852 			xml = xml + (" offensive=\"" + this.offensive + "\"");
6853 		}
6854 		if (this.learn != null) {
6855 			xml = xml + (" learn=\"" + this.learn + "\"");
6856 		}
6857 		if (this.secure != null) {
6858 			xml = xml + (" secure=\"" + this.secure + "\"");
6859 		}
6860 		if (this.plainText != null) {
6861 			xml = xml + (" plainText=\"" + this.plainText + "\"");
6862 		}
6863 		if (this.debug) {
6864 			xml = xml + (" debug=\"" + this.debug + "\"");
6865 		}
6866 		if (this.info) {
6867 			xml = xml + (" info=\"" + SDK.escapeHTML(this.info) + "\"");
6868 		}
6869 		if (this.debugLevel != null) {
6870 			xml = xml + (" debugLevel=\"" + this.debugLevel + "\"");
6871 		}
6872 		if (this.disconnect) {
6873 			xml = xml + (" disconnect=\"" + this.disconnect + "\"");
6874 		}
6875 		xml = xml + (">");
6876 		
6877 		if (this.message != null) {
6878 			xml = xml + ("<message>");
6879 			xml = xml + (SDK.escapeHTML(this.message));
6880 			xml = xml + ("</message>");
6881 		}
6882 		xml = xml + ("</chat>");
6883 		return xml;
6884 	}
6885 }
6886 ChatConfig.prototype = new Config();
6887 ChatConfig.prototype.constructor = ChatConfig;
6888 ChatConfig.constructor = ChatConfig;
6889 
6890 /**
6891  * This object models a command message sent to a bot instance.
6892  * It can be used to send JSON events and commands to the bot to process.
6893  * It can convert itself to XML for web API usage.
6894  * @class
6895  * @property conversation
6896  * @property speak
6897  * @property correction
6898  * @property offensive
6899  * @property disconnect
6900  * @property emote
6901  * @property action
6902  * @property message
6903  * @property debug
6904  * @property debugLevel
6905  * @property learn
6906  */
6907 function CommandConfig() {
6908 	/** The conversation id for the message.  This will be returned from the first response, and must be used for all subsequent messages to maintain the conversational state.  Without the conversation id, the bot has no context for the reply. */
6909 	this.conversation;
6910 	/** Sets if the voice audio should be generated for the bot's response. */
6911 	this.speak;
6912 	/** Sets the message to be a correction to the bot's last response. */
6913 	this.correction;
6914 	/** Flags the bot's last response as offensive. */
6915 	this.offensive;
6916 	/** Ends the conversation. Conversation should be terminated to converse server resources.  The message can be blank. */
6917 	this.disconnect;
6918 	/** 
6919 	 * Attaches an emotion to the user's message, one of:
6920 	 *  NONE,
6921 	 *  LOVE, LIKE, DISLIKE, HATE,
6922 	 *	RAGE, ANGER, CALM, SERENE,
6923 	 *	ECSTATIC, HAPPY, SAD, CRYING,
6924 	 *	PANIC, AFRAID, CONFIDENT, COURAGEOUS,
6925 	 *	SURPRISE, BORED,
6926 	 *	LAUGHTER, SERIOUS
6927 	 */
6928 	this.emote;
6929 	/** Attaches an action to the user's messages, such as "laugh", "smile", "kiss". */
6930 	this.action;
6931 	/** The json command. */
6932 	this.command;
6933 	/** Include the message debug log in the response. */
6934 	this.debug;
6935 	/** Set the debug level, one of: SEVER, WARNING, INFO, CONFIG, FINE, FINER. */
6936 	this.debugLevel;
6937 	/** Enable or disable the bot's learning for this message. */
6938 	this.learn;
6939 	/** Escape and filter the response message HTML content for XSS security. */
6940 	this.secure = SDK.secure;
6941 	/** Strip any HTML tags from the response message. */
6942 	this.plainText;
6943 	/** Send extra info with the message, such as the user's contact info (name email phone). */
6944 	this.info;
6945 	/** Request a specific avatar (by ID). */
6946 	this.avatar;
6947 	/** Request the response avatar media in HD. */
6948 	this.avatarHD = SDK.hd;
6949 	/** Request the response avatar media in a video or image format. */
6950 	this.avatarFormat = SDK.format;
6951 	/** Translate between the user's language and the bot's language. */
6952 	this.language;
6953 	
6954 	this.toXML = function() {
6955 		var xml = "<command";
6956 		xml = this.writeCredentials(xml);
6957 		if (this.conversation != null) {
6958 			xml = xml + (" conversation=\"" + this.conversation + "\"");
6959 		}
6960 		if (this.emote != null) {
6961 			xml = xml + (" emote=\"" + this.emote + "\"");
6962 		}
6963 		if (this.action != null) {
6964 			xml = xml + (" action=\"" + this.action + "\"");
6965 		}
6966 		if (this.speak) {
6967 			xml = xml + (" speak=\"" + this.speak + "\"");
6968 		}
6969 		if (this.language != null) {
6970 			xml = xml + (" language=\"" + this.language + "\"");
6971 		}
6972 		if (this.avatarHD) {
6973 			xml = xml + (" avatarHD=\"" + this.avatarHD + "\"");
6974 		}
6975 		if (this.avatarFormat != null) {
6976 			xml = xml + (" avatarFormat=\"" + this.avatarFormat + "\"");
6977 		}
6978 		if (this.correction) {
6979 			xml = xml + (" correction=\"" + this.correction + "\"");
6980 		}
6981 		if (this.offensive) {
6982 			xml = xml + (" offensive=\"" + this.offensive + "\"");
6983 		}
6984 		if (this.learn != null) {
6985 			xml = xml + (" learn=\"" + this.learn + "\"");
6986 		}
6987 		if (this.secure != null) {
6988 			xml = xml + (" secure=\"" + this.secure + "\"");
6989 		}
6990 		if (this.plainText != null) {
6991 			xml = xml + (" plainText=\"" + this.plainText + "\"");
6992 		}
6993 		if (this.debug) {
6994 			xml = xml + (" debug=\"" + this.debug + "\"");
6995 		}
6996 		if (this.info) {
6997 			xml = xml + (" info=\"" + SDK.escapeHTML(this.info) + "\"");
6998 		}
6999 		if (this.debugLevel != null) {
7000 			xml = xml + (" debugLevel=\"" + this.debugLevel + "\"");
7001 		}
7002 		if (this.disconnect) {
7003 			xml = xml + (" disconnect=\"" + this.disconnect + "\"");
7004 		}
7005 		xml = xml + (">");
7006 		
7007 		if (this.command != null) {
7008 			xml = xml + ("<command>");
7009 			xml = xml + (SDK.escapeHTML(this.command));
7010 			xml = xml + ("</command>");
7011 		}
7012 		xml = xml + ("</command>");
7013 		return xml;
7014 	}
7015 }
7016 CommandConfig.prototype = new Config();
7017 CommandConfig.prototype.constructor = CommandConfig;
7018 CommandConfig.constructor = CommandConfig;
7019 
7020 /**
7021  * This object models a chat message received from a chat bot instance.
7022  * It can be used from a chat UI, or with the Libre Web API.
7023  * It can convert itself from XML for web API usage.
7024  * @class
7025  * @property conversation
7026  * @property avatar
7027  * @property avatarType
7028  * @property avatarTalk
7029  * @property avatarTalkType
7030  * @property avatarAction
7031  * @property avatarActionType
7032  * @property avatarActionAudio
7033  * @property avatarActionAudioType
7034  * @property avatarAudio
7035  * @property avatarAudioType
7036  * @property avatarBackground
7037  * @property speech
7038  * @property message
7039  * @property question
7040  * @property emote
7041  * @property action
7042  * @property pose
7043  * @property command
7044  * @property log
7045  */
7046 function ChatResponse() {	
7047 	/** The conversation id for the message.  This will be returned from the first response, and must be used for all subsequent messages to maintain the conversational state.  Without the conversation id, the bot has no context for the reply. */
7048 	this.conversation;
7049 	/** Server relative URL for the avatar image or video. */
7050 	this.avatar;
7051 	/** Second avatar animation. */
7052 	this.avatar2;
7053 	/** Third avatar animation. */
7054 	this.avatar3;
7055 	/** Forth avatar animation. */
7056 	this.avatar4;
7057 	/** Fifth avatar animation. */
7058 	this.avatar5;
7059 	/** Avatar MIME file type, (mpeg, webm, ogg, jpeg, png) */
7060 	this.avatarType;
7061 	/** Server relative URL for the avatar talking image or video. */
7062 	this.avatarTalk;
7063 	/** Avatar talk MIME file type, (mpeg, webm, ogg, jpeg, png) */
7064 	this.avatarTalkType;
7065 	/** Server relative URL for the avatar action image or video. */
7066 	this.avatarAction;
7067 	/** Avatar action MIME file type, (mpeg, webm, ogg, jpeg, png) */
7068 	this.avatarActionType;
7069 	/** Server relative URL for the avatar action audio image or video. */
7070 	this.avatarActionAudio;
7071 	/** Avatar action audio MIME file type,  (mpeg, wav) */
7072 	this.avatarActionAudioType;
7073 	/** Server relative URL for the avatar audio image or video. */
7074 	this.avatarAudio;
7075 	/** Avatar audio MIME file type,  (mpeg, wav) */
7076 	this.avatarAudioType;
7077 	/** Server relative URL for the avatar background image. */
7078 	this.avatarBackground;
7079 	/** Server relative URL for the avatar speech audio file. */
7080 	this.speech;
7081 	/** The bot's message text. */
7082 	this.message;
7083 	/** Optional text to the original question. */
7084 	this.question;
7085 	/**
7086 	 * Emotion attached to the bot's message, one of:
7087 	 *  NONE,
7088 	 *  LOVE, LIKE, DISLIKE, HATE,
7089 	 *	RAGE, ANGER, CALM, SERENE,
7090 	 *	ECSTATIC, HAPPY, SAD, CRYING,
7091 	 *	PANIC, AFRAID, CONFIDENT, COURAGEOUS,
7092 	 *	SURPRISE, BORED,
7093 	 *	LAUGHTER, SERIOUS
7094 	 */
7095 	this.emote;
7096 	/** Action for the bot's messages, such as "laugh", "smile", "kiss", or mobile directive (for virtual assistants). */
7097 	this.action;
7098 	/** Pose for the bot's messages, such as "dancing", "sitting", "sleeping". */
7099 	this.pose;
7100 	/** JSON Command for the bot's message. This can be by the client for mobile virtual assistant functionality, games integration, or other uses.  */
7101 	this.command;
7102 	/** The debug log of processing the message. */
7103 	this.log;
7104 
7105 	this.parseXML = function(element) {
7106 		this.conversation = element.getAttribute("conversation");
7107 		this.avatar = element.getAttribute("avatar");
7108 		this.avatar2 = element.getAttribute("avatar2");
7109 		this.avatar3 = element.getAttribute("avatar3");
7110 		this.avatar4 = element.getAttribute("avatar4");
7111 		this.avatar5 = element.getAttribute("avatar5");
7112 		this.avatarType = element.getAttribute("avatarType");
7113 		this.avatarTalk = element.getAttribute("avatarTalk");
7114 		this.avatarTalkType = element.getAttribute("avatarTalkType");
7115 		this.avatarAction = element.getAttribute("avatarAction");
7116 		this.avatarActionType = element.getAttribute("avatarActionType");
7117 		this.avatarActionAudio = element.getAttribute("avatarActionAudio");
7118 		this.avatarActionAudioType = element.getAttribute("avatarActionAudioType");
7119 		this.avatarAudio = element.getAttribute("avatarAudio");
7120 		this.avatarAudioType = element.getAttribute("avatarAudioType");
7121 		this.avatarBackground = element.getAttribute("avatarBackground");
7122 		this.emote = element.getAttribute("emote");
7123 		this.action = element.getAttribute("action");
7124 		this.pose = element.getAttribute("pose");
7125 		this.command = element.getAttribute("command");
7126 		this.speech = element.getAttribute("speech");
7127 
7128 		var node = element.getElementsByTagName("message")[0];
7129 		if (node != null) {
7130 			this.message = SDK.innerHTML(node);
7131 		}
7132 		
7133 		node = element.getElementsByTagName("log")[0];
7134 		if (node != null) {
7135 			this.log = SDK.innerHTML(node);
7136 		}
7137 	}
7138 }
7139 ChatResponse.prototype = new Config();
7140 ChatResponse.prototype.constructor = ChatResponse;
7141 ChatResponse.constructor = ChatResponse;
7142 
7143 /**
7144  * This object is returned from the SDK chatSettings() API to retrieve a conversation's chat settings.
7145  * It can convert itself from XML for web API usage.
7146  * @class
7147  * @property conversation
7148  * @property allowEmotes
7149  * @property allowCorrection
7150  * @property allowLearning
7151  * @property learning
7152  */
7153 function ChatSettings() {
7154 	this.conversation;
7155 	this.allowEmotes;
7156 	this.allowCorrection;
7157 	this.allowLearning;
7158 	this.learning;
7159 
7160 	this.toXML = function() {
7161 		var xml = "<chat-settings";
7162 		xml = this.writeCredentials(xml);
7163 		if (this.conversation != null) {
7164 			xml = xml + (" conversation=\"" + this.conversation + "\"");
7165 		}
7166 		xml = xml + ("/>");
7167 		return xml;
7168 	}
7169 	
7170 	this.parseXML = function(element) {
7171 		this.allowEmotes = "true" == (element.getAttribute("allowEmotes"));
7172 		this.allowCorrection = "true" == (element.getAttribute("allowCorrection"));
7173 		this.allowLearning = "true" == (element.getAttribute("allowLearning"));
7174 		this.learning = "true" == (element.getAttribute("learning"));
7175 	}
7176 }
7177 ChatSettings.prototype = new Config();
7178 ChatSettings.prototype.constructor = ChatSettings;
7179 ChatSettings.constructor = ChatSettings;
7180 
7181 /**
7182  * DTO for XML avatar message config.
7183  * @class
7184  * @property avatar
7185  * @property speak
7186  * @property voice
7187  * @property message
7188  * @property emote
7189  * @property action
7190  * @property pose
7191  */
7192 function AvatarMessage() {
7193 	this.avatar;
7194 	this.speak;
7195 	this.voice;
7196 	this.voiceMod;
7197 	this.message;
7198 	this.emote;
7199 	this.action;
7200 	this.pose;
7201 	this.hd = SDK.hd;
7202 	this.format = SDK.format;
7203 	
7204 	this.toXML = function() {
7205 		var xml = "<avatar-message";
7206 		xml = this.writeCredentials(xml);
7207 		if (this.avatar != null) {
7208 			xml = xml + (" avatar=\"" + this.avatar + "\"");
7209 		}
7210 		if (this.emote != null) {
7211 			xml = xml + (" emote=\"" + this.emote + "\"");
7212 		}
7213 		if (this.action != null) {
7214 			xml = xml + (" action=\"" + this.action + "\"");
7215 		}
7216 		if (this.pose != null) {
7217 			xml = xml + (" pose=\"" + this.pose + "\"");
7218 		}
7219 		if (this.voice != null) {
7220 			xml = xml + (" voice=\"" + this.voice + "\"");
7221 		}
7222 		if (this.voiceMod != null) {
7223 			xml = xml + (" voiceMod=\"" + this.voiceMod + "\"");
7224 		}
7225 		if (this.format != null) {
7226 			xml = xml + (" format=\"" + this.format + "\"");
7227 		}
7228 		if (this.speak) {
7229 			xml = xml + (" speak=\"" + this.speak + "\"");
7230 		}
7231 		if (this.hd) {
7232 			xml = xml + (" hd=\"" + this.hd + "\"");
7233 		}
7234 		xml = xml + (">");
7235 		
7236 		if (this.message != null) {
7237 			xml = xml + ("<message>");
7238 			xml = xml + (SDK.escapeHTML(this.message));
7239 			xml = xml + ("</message>");
7240 		}
7241 		xml = xml + ("</avatar-message>");
7242 		return xml;
7243 	}
7244 }
7245 AvatarMessage.prototype = new Config();
7246 AvatarMessage.prototype.constructor = AvatarMessage;
7247 AvatarMessage.constructor = AvatarMessage;
7248 
7249 /**
7250  * This object models the web API browse operation.
7251  * It can be used to search a set of instances (bots, forums, or channels).
7252  * @class
7253  * @property type
7254  * @property typeFilter
7255  * @property category
7256  * @property tag
7257  * @property filter
7258  * @property sort
7259  */
7260 function BrowseConfig() {
7261 	/** Filters instances by access type, "Public", "Private", "Personal". */
7262 	this.typeFilter;
7263 	/** Filters instances by categories (csv) */
7264 	this.category;
7265 	/** Filters instances by tags (csv) */
7266 	this.tag;
7267 	/** Filters instances by name */
7268 	this.filter;
7269 	/** Sorts instances, "name", "date", "size", "stars", "thumbs up", "thumbs down", "last connect", "connects", "connects today", "connects this week ", "connects this month" */
7270 	this.sort;
7271 	
7272 	this.toXML = function() {
7273 		var xml = "<browse";
7274 		xml = this.writeCredentials(xml);
7275 		if (this.typeFilter != null) {
7276 			xml = xml + (" typeFilter=\"" + this.typeFilter + "\"");
7277 		}
7278 		if (this.sort != null) {
7279 			xml = xml + (" sort=\"" + this.sort + "\"");
7280 		}
7281 		if ((this.category != null) && this.category != "") {
7282 			xml = xml + (" category=\"" + this.category + "\"");
7283 		}
7284 		if ((this.tag != null) && this.tag != "") {
7285 			xml = xml + (" tag=\"" + this.tag + "\"");
7286 		}
7287 		if ((this.filter != null) && this.filter != "") {
7288 			xml = xml + (" filter=\"" + this.filter + "\"");
7289 		}
7290 		xml = xml + ("/>");
7291 		return xml;
7292 	}
7293 }
7294 BrowseConfig.prototype = new Config();
7295 BrowseConfig.prototype.constructor = BrowseConfig;
7296 BrowseConfig.constructor = BrowseConfig;
7297 
7298 /**
7299  * Abstract content class.
7300  * This object models a content object such as a bot, forum, or channel.
7301  * It can be used from a chat UI, or with the Libre Web API.
7302  * It can convert itself to/from XML for web API usage.
7303  * This can be used to create, edit, or browse a content.
7304  * @class
7305  * @property id
7306  * @property name
7307  * @property isAdmin
7308  * @property isAdult
7309  * @property isPrivate
7310  * @property isHidden
7311  * @property accessMode
7312  * @property isFlagged
7313  * @property flaggedReason
7314  * @property isExternal
7315  * @property description
7316  * @property details
7317  * @property disclaimer
7318  * @property tags
7319  * @property categories
7320  * @property creator
7321  * @property creationDate
7322  * @property lastConnectedUser
7323  * @property website
7324  * @property license
7325  * @property avatar
7326  * @property connects
7327  * @property dailyConnects
7328  * @property weeklyConnects
7329  * @property monthlyConnects
7330  */
7331 function WebMediumConfig() {
7332 	/** Instance ID. */
7333 	this.id;
7334 	/** Instance name. */
7335 	this.name;
7336 	/** Read-only, returns if connected user is the content's admin. */
7337 	this.isAdmin;
7338 	this.isAdult;
7339 	/** Sets if the content is private to the creator, and its members. */
7340 	this.isPrivate;
7341 	/** Sets if the conent will be visible and searchable in the content directory. */
7342 	this.isHidden;
7343 	/** Sets the access mode for the content, ("Everyone", "Users", "Members", "Administrators"). */
7344 	this.accessMode;
7345 	/** Returns if the content has been flagged, or used to flag content as offensive (reason required). */
7346 	this.isFlagged;
7347 	/** Returns why the content has been flagged, or used to flag content as offensive. */
7348 	this.flaggedReason;
7349 	/** Can be used to create a link to external content in the content directory. */
7350 	this.isExternal;
7351 	/** Optional description of the content. */
7352 	this.description;
7353 	/** Optional restrictions or details of the content. */
7354 	this.details;
7355 	/** Optional warning or disclaimer of the content. */
7356 	this.disclaimer;
7357 	/** Tags to classify the content (csv). */
7358 	this.tags;
7359 	/** Categories to categorize the content under (csv). */
7360 	this.categories;
7361 	/** Read-only, returns content's creator's user ID. */
7362 	this.creator;
7363 	/** Read-only, returns content's creation date. */
7364 	this.creationDate;
7365 	/** Read-only, returns last user to access content */
7366 	this.lastConnectedUser;
7367 	/** Optional license to license the content under. */
7368 	this.license;
7369 	/** Optional website related to the content. */
7370 	this.website = "";
7371 	/** Read-only, server local URL to content's avatar image. */
7372 	this.avatar;
7373 	/** Read-only, returns content's toal connects. */
7374 	this.connects;
7375 	/** Read-only, returns content's daily connects. */
7376 	this.dailyConnects;
7377 	/** Read-only, returns content's weekly connects. */
7378 	this.weeklyConnects;
7379 	/** Read-only, returns content's monthly connects. */
7380 	this.monthlyConnects;
7381 
7382 	this.writeWebMediumXML = function(xml) {
7383 		xml = this.writeCredentials(xml);
7384 		if (this.id != null) {
7385 			xml = xml + (" id=\"" + this.id + "\"");
7386 		}
7387 		if (this.name != null) {
7388 			xml = xml + (" name=\"" + this.name + "\"");
7389 		}
7390 		if (this.isPrivate) {
7391 			xml = xml + (" isPrivate=\"true\"");
7392 		}
7393 		if (this.isHidden) {
7394 			xml = xml + (" isHidden=\"true\"");
7395 		}
7396 		if (this.accessMode != null && this.accessMode != "") {
7397 			xml = xml + (" accessMode=\"" + this.accessMode + "\"");
7398 		}
7399 		if (this.isAdult) {
7400 			xml = xml + (" isAdult=\"true\"");
7401 		}
7402 		if (this.isFlagged) {
7403 			xml = xml + (" isFlagged=\"true\"");
7404 		}
7405 		xml = xml + (">");
7406 		if (this.description != null) {
7407 			xml = xml + ("<description>");
7408 			xml = xml + (SDK.escapeHTML(this.description));
7409 			xml = xml + ("</description>");
7410 		}
7411 		if (this.details != null) {
7412 			xml = xml + ("<details>");
7413 			xml = xml + (SDK.escapeHTML(this.details));
7414 			xml = xml + ("</details>");
7415 		}
7416 		if (this.disclaimer != null) {
7417 			xml = xml + ("<disclaimer>");
7418 			xml = xml + (SDK.escapeHTML(this.disclaimer));
7419 			xml = xml + ("</disclaimer>");
7420 		}
7421 		if (this.categories != null) {
7422 			xml = xml + ("<categories>");
7423 			xml = xml + (SDK.escapeHTML(this.categories));
7424 			xml = xml + ("</categories>");
7425 		}
7426 		if (this.tags != null) {
7427 			xml = xml + ("<tags>");
7428 			xml = xml + (SDK.escapeHTML(this.tags));
7429 			xml = xml + ("</tags>");
7430 		}
7431 		if (this.license != null) {
7432 			xml = xml + ("<license>");
7433 			xml = xml + (SDK.escapeHTML(this.license));
7434 			xml = xml + ("</license>");
7435 		}
7436 		if (this.flaggedReason != null) {
7437 			xml = xml + ("<flaggedReason>");
7438 			xml = xml + (SDK.escapeHTML(this.flaggedReason));
7439 			xml = xml + ("</flaggedReason>");
7440 		}
7441 		return xml;
7442 	}
7443 	
7444 	this.parseWebMediumXML = function(element) {
7445 		this.id = element.getAttribute("id");
7446 		this.name = element.getAttribute("name");
7447 		this.creationDate = element.getAttribute("creationDate");
7448 		this.isPrivate = element.getAttribute("isPrivate");
7449 		this.isHidden = element.getAttribute("isHidden");
7450 		this.accessMode = element.getAttribute("accessMode");
7451 		this.isAdmin = element.getAttribute("isAdmin");
7452 		this.isAdult = element.getAttribute("isAdult");
7453 		this.isFlagged = element.getAttribute("isFlagged");
7454 		this.creator = element.getAttribute("creator");
7455 		this.creationDate = element.getAttribute("creationDate");
7456 		this.connects = element.getAttribute("connects");
7457 		this.dailyConnects = element.getAttribute("dailyConnects");
7458 		this.weeklyConnects = element.getAttribute("weeklyConnects");
7459 		this.monthlyConnects = element.getAttribute("monthlyConnects");
7460 		
7461 		var node = element.getElementsByTagName("description")[0];
7462 		if (node != null) {
7463 			this.description = SDK.innerHTML(node);
7464 		}
7465 		node = element.getElementsByTagName("details")[0];
7466 		if (node != null) {
7467 			this.details = SDK.innerHTML(node);
7468 		}
7469 		node = element.getElementsByTagName("disclaimer")[0];
7470 		if (node != null) {
7471 			this.disclaimer = SDK.innerHTML(node);
7472 		}
7473 		node = element.getElementsByTagName("categories")[0];
7474 		if (node != null) {
7475 			this.categories = SDK.innerHTML(node);
7476 		}
7477 		node = element.getElementsByTagName("tags")[0];
7478 		if (node != null) {
7479 			this.tags = SDK.innerHTML(node);
7480 		}
7481 		node = element.getElementsByTagName("flaggedReason")[0];
7482 		if (node != null) {
7483 			this.flaggedReason = SDK.innerHTML(node);
7484 		}
7485 		node = element.getElementsByTagName("lastConnectedUser")[0];
7486 		if (node != null) {
7487 			this.lastConnectedUser = SDK.innerHTML(node);
7488 		}
7489 		node = element.getElementsByTagName("license")[0];
7490 		if (node != null) {
7491 			this.license = SDK.innerHTML(node);
7492 		}
7493 		node = element.getElementsByTagName("avatar")[0];
7494 		if (node != null) {
7495 			this.avatar = SDK.innerHTML(node);
7496 		}
7497 	}
7498 }
7499 WebMediumConfig.prototype = new Config();
7500 WebMediumConfig.prototype.constructor = WebMediumConfig;
7501 WebMediumConfig.constructor = WebMediumConfig;
7502 
7503 /**
7504  * This object models a live chat channel or chatroom instance.
7505  * It can be used from a chat UI, or with the Libre Web API.
7506  * It can convert itself to/from XML for web API usage.
7507  * This can be used to create, edit, or browse a channel instance.
7508  * @class
7509  * @property type
7510  * @property messages
7511  * @property usersOnline
7512  * @property adminsOnline
7513  */
7514 function ChannelConfig() {
7515 	/** Sets type, "ChatRoom", "OneOnOne". */
7516 	this.type;
7517 	/** Read-only: total number of messages. */
7518 	this.messages;
7519 	/** Read-only: current users online. */
7520 	this.usersOnline;
7521 	/** Read-only: current admins or operators online. */
7522 	this.adminsOnline;
7523 	
7524 	this.type = "channel";
7525 	
7526 	this.credentials = function() {
7527 		var config = new ChannelConfig();
7528 		config.id = this.id;
7529 		return config;
7530 	}
7531 	
7532 	this.toXML = function() {
7533 		var xml = "<channel";
7534 		if (this.type != null && this.type != "") {
7535 			xml = xml + (" type=\"" + this.type + "\"");
7536 		}
7537 		xml = this.writeWebMediumXML(xml);
7538 		xml = xml + ("</channel>");
7539 		return xml;
7540 	}
7541 	
7542 	this.parseXML = function(element) {
7543 		this.parseWebMediumXML(element);
7544 		this.type = element.getAttribute("type");
7545 		this.messages = element.getAttribute("messages");
7546 		this.usersOnline = element.getAttribute("usersOnline");
7547 		this.adminsOnline = element.getAttribute("adminsOnline");
7548 	}
7549 }
7550 ChannelConfig.prototype = new WebMediumConfig();
7551 ChannelConfig.prototype.constructor = ChannelConfig;
7552 ChannelConfig.constructor = ChannelConfig;
7553 
7554 /**
7555  * DTO to parse response of a list of names.
7556  * This is used for categories, tags, and templates.
7557  * @class
7558  * @property type
7559  */
7560 function ContentConfig() {	
7561 	this.type;	
7562 	
7563 	this.parseXML = function(element) {		
7564 		this.type = element.getAttribute("type");
7565 	}
7566 
7567 	
7568 	this.toXML = function() {
7569 		var xml = "<content";
7570 		xml = this.writeCredentials(xml);
7571 		
7572 		xml = xml + ("/>");
7573 		return xml;
7574 	}
7575 }
7576 ContentConfig.prototype = new Config();
7577 ContentConfig.prototype.constructor = ContentConfig;
7578 ContentConfig.constructor = ContentConfig;
7579 
7580 /**
7581  * This object models a domain.
7582  * It can be used from a chat UI, or with the Libre Web API.
7583  * A domain is an isolated content space to create bots and other content in (such as a commpany, project, or school).
7584  * It can convert itself to/from XML for web API usage.
7585  * This can be used to create, edit, or browse a domain instance.
7586  * @class
7587  * @property creationMode
7588  */
7589 function DomainConfig() {
7590 	this.creationMode;
7591 	
7592 	this.type = "domain";
7593 	
7594 	this.credentials = function() {
7595 		var config = new DomainConfig();
7596 		config.id = this.id;
7597 		return config;
7598 	}
7599 	
7600 	this.toXML = function() {
7601 		var xml = "<domain";
7602 		if (this.creationMode != null && this.creationMode != "") {
7603 			xml = xml + (" creationMode=\"" + this.creationMode + "\"");
7604 		}
7605 		xml = this.writeWebMediumXML(xml);
7606 		xml = xml + ("</domain>");
7607 		return xml;
7608 	}
7609 	
7610 	this.parseXML = function(element) {
7611 		this.parseWebMediumXML(element);
7612 		this.creationMode = element.getAttribute("creationMode");
7613 	}
7614 }
7615 DomainConfig.prototype = new WebMediumConfig();
7616 DomainConfig.prototype.constructor = DomainConfig;
7617 DomainConfig.constructor = DomainConfig;
7618 
7619 /**
7620  * This object models an avatar.
7621  * An avatar represents a bot's visual image, but can also be used independently with TTS.
7622  * It can convert itself to/from XML for web API usage.
7623  * This can be used to create, edit, or browse an avatar instance.
7624  * @class
7625  */
7626 function AvatarConfig() {
7627 	
7628 	this.type = "avatar";
7629 	
7630 	this.credentials = function() {
7631 		var config = new AvatarConfig();
7632 		config.id = this.id;
7633 		return config;
7634 	}
7635 	
7636 	this.toXML = function() {
7637 		var xml = "<avatar";
7638 		xml = this.writeWebMediumXML(xml);
7639 		xml = xml + ("</avatar>");
7640 		return xml;
7641 	}
7642 	
7643 	this.parseXML = function(element) {
7644 		this.parseWebMediumXML(element);
7645 	}
7646 }
7647 AvatarConfig.prototype = new WebMediumConfig();
7648 AvatarConfig.prototype.constructor = AvatarConfig;
7649 AvatarConfig.constructor = AvatarConfig;
7650 
7651 /**
7652  * This object models a script from the script library.
7653  * It can convert itself to/from XML for web API usage.
7654  * This can be used to create, edit, or browse an avatar instance.
7655  * @class
7656  */
7657 function ScriptConfig() {
7658 	
7659 	this.type = "script";
7660 	
7661 	this.credentials = function() {
7662 		var config = new AvatarConfig();
7663 		config.id = this.id;
7664 		return config;
7665 	}
7666 	
7667 	this.toXML = function() {
7668 		var xml = "<script";
7669 		xml = this.writeWebMediumXML(xml);
7670 		xml = xml + ("</script>");
7671 		return xml;
7672 	}
7673 	
7674 	this.parseXML = function(element) {
7675 		this.parseWebMediumXML(element);
7676 	}
7677 }
7678 ScriptConfig.prototype = new WebMediumConfig();
7679 ScriptConfig.prototype.constructor = ScriptConfig;
7680 ScriptConfig.constructor = ScriptConfig;
7681 
7682 /**
7683  * This object models a graphic from the graphics library.
7684  * It can convert itself to/from XML for web API usage.
7685  * This can be used to create, edit, or browse an avatar instance.
7686  * @class
7687  */
7688 function GraphicConfig() {
7689 	this.media;
7690 	
7691 	this.type = "graphic";
7692 	
7693 	this.credentials = function() {
7694 		var config = new AvatarConfig();
7695 		config.id = this.id;
7696 		return config;
7697 	}
7698 	
7699 	this.toXML = function() {
7700 		var xml = "<graphic";
7701 		xml = this.writeWebMediumXML(xml);
7702 		xml = xml + ("</graphic>");
7703 		return xml;
7704 	}
7705 	
7706 	this.parseXML = function(element) {
7707 		this.parseWebMediumXML(element);
7708 		this.media = element.getAttribute("media");
7709 	}
7710 }
7711 GraphicConfig.prototype = new WebMediumConfig();
7712 GraphicConfig.prototype.constructor = GraphicConfig;
7713 GraphicConfig.constructor = GraphicConfig;
7714 
7715 /**
7716  * This object models a forum instance.
7717  * It can be used from a chat UI, or with the Libre Web API.
7718  * It can convert itself to/from XML for web API usage.
7719  * This can be used to create, edit, or browse a forum instance.
7720  * @class
7721  * @property replyAccessMode
7722  * @property postAccessMode
7723  * @property posts
7724  */
7725 function ForumConfig() {
7726 	/** Sets the access mode for forum post replies, ("Everyone", "Users", "Members", "Administrators"). */
7727 	this.replyAccessMode;
7728 	/** Sets the access mode for forum posts, ("Everyone", "Users", "Members", "Administrators"). */
7729 	this.postAccessMode;
7730 	/** Read-only property for the total number of posts to the forum. */
7731 	this.posts;
7732 	
7733 	this.type = "forum";
7734 	
7735 	this.credentials = function() {
7736 		var config = new ForumConfig();
7737 		config.id = this.id;
7738 		return config;
7739 	}
7740 	
7741 	this.toXML = function() {
7742 		var xml = xml + ("<forum");
7743 		if (this.replyAccessMode != null && !this.replyAccessMode == "") {
7744 			xml = xml + (" replyAccessMode=\"" + this.replyAccessMode + "\"");
7745 		}
7746 		if (this.postAccessMode != null && !this.postAccessMode == "") {
7747 			xml = xml + (" postAccessMode=\"" + this.postAccessMode + "\"");
7748 		}
7749 		xml = this.writeWebMediumXML(xml);
7750 		xml = xml + ("</forum>");
7751 		return xml;
7752 	}
7753 	
7754 	this.parseXML = function(element) {
7755 		this.parseWebMediumXML(element);
7756 		this.replyAccessMode = element.getAttribute("replyAccessMode");
7757 		this.postAccessMode = element.getAttribute("postAccessMode");
7758 		this.posts = element.getAttribute("posts");
7759 	}
7760 }
7761 ForumConfig.prototype = new WebMediumConfig();
7762 ForumConfig.prototype.constructor = ForumConfig;
7763 ForumConfig.constructor = ForumConfig;
7764 
7765 /**
7766  * This object models a forum post.
7767  * It can be used from a forum UI, or with the Libre Web API.
7768  * It can convert itself to/from XML for web API usage.
7769  * You must set the forum id as the forum of the forum post.
7770  * A forum post that has a parent (parent forum post id) is a reply.
7771  * @class
7772  * @property id
7773  * @property topic
7774  * @property summary
7775  * @property details
7776  * @property detailsText
7777  * @property forum
7778  * @property tags
7779  * @property isAdmin
7780  * @property isFlagged
7781  * @property flaggedReason
7782  * @property isFeatured
7783  * @property creator
7784  * @property creationDate
7785  * @property views
7786  * @property dailyViews
7787  * @property weeklyViews
7788  * @property monthlyViews
7789  * @property replyCount
7790  * @property parent
7791  * @property replies
7792  */
7793 function ForumPostConfig() {	
7794 	this.id;
7795 	this.topic;
7796 	this.summary;
7797 	this.details;
7798 	this.detailsText;
7799 	this.forum;
7800 	this.tags;
7801 	this.isAdmin;
7802 	this.isFlagged;
7803 	this.flaggedReason;
7804 	this.isFeatured;
7805 	this.creator;
7806 	this.creationDate;
7807 	this.views;
7808 	this.dailyViews;
7809 	this.weeklyViews;
7810 	this.monthlyViews;
7811 	this.replyCount;
7812 	this.parent;
7813 	this.avatar;
7814 	this.replies;
7815 	
7816 	this.toXML = function() {
7817 		var xml = "<forum-post";
7818 		xml = this.writeCredentials(xml);
7819 		if (this.id != null) {
7820 			xml = xml + (" id=\"" + this.id + "\"");
7821 		}
7822 		if (this.parent != null) {
7823 			xml = xml + (" parent=\"" + this.parent + "\"");
7824 		}
7825 		if (this.forum != null) {
7826 			xml = xml + (" forum=\"" + this.forum + "\"");
7827 		}
7828 		if (this.isFeatured) {
7829 			xml = xml + (" isFeatured=\"true\"");
7830 		}
7831 		xml = xml + (">");
7832 		if (this.topic != null) {
7833 			xml = xml + ("<topic>");
7834 			xml = xml + (SDK.escapeHTML(this.topic));
7835 			xml = xml + ("</topic>");
7836 		}
7837 		if (this.details != null) {
7838 			xml = xml + ("<details>");
7839 			xml = xml + (SDK.escapeHTML(this.details));
7840 			xml = xml + ("</details>");
7841 		}
7842 		if (this.tags != null) {
7843 			xml = xml + ("<tags>");
7844 			xml = xml + (SDK.escapeHTML(this.tags));
7845 			xml = xml + ("</tags>");
7846 		}
7847 		xml = xml + ("</forum-post>");
7848 	}
7849 	
7850 	this.parseXML = function(element) {
7851 		this.id = element.getAttribute("id");
7852 		this.parent = element.getAttribute("parent");
7853 		this.forum = element.getAttribute("forum");
7854 		this.views = element.getAttribute("views");
7855 		this.dailyViews = element.getAttribute("dailyViews");
7856 		this.weeklyViews = element.getAttribute("weeklyViews");
7857 		this.monthlyViews = element.getAttribute("monthlyViews");
7858 		this.isAdmin = element.getAttribute("isAdmin");
7859 		this.replyCount = element.getAttribute("replyCount");
7860 		this.isFlagged = element.getAttribute("isFlagged");
7861 		this.isFeatured = element.getAttribute("isFeatured");
7862 		this.creator = element.getAttribute("creator");
7863 		this.creationDate = element.getAttribute("creationDate");
7864 		
7865 		var node = element.getElementsByTagName("summary")[0];
7866 		if (node != null) {
7867 			this.summary = SDK.innerHTML(node);
7868 		}
7869 		node = element.getElementsByTagName("details")[0];
7870 		if (node != null) {
7871 			this.details = SDK.innerHTML(node);
7872 		}
7873 		node = element.getElementsByTagName("detailsText")[0];
7874 		if (node != null) {
7875 			this.detailsText = SDK.innerHTML(node);
7876 		}
7877 		node = element.getElementsByTagName("topic")[0];
7878 		if (node != null) {
7879 			this.topic = SDK.innerHTML(node);
7880 		}
7881 		node = element.getElementsByTagName("tags")[0];
7882 		if (node != null) {
7883 			this.tags = SDK.innerHTML(node);
7884 		}
7885 		node = element.getElementsByTagName("flaggedReason")[0];
7886 		if (node != null) {
7887 			this.flaggedReason = SDK.innerHTML(node);
7888 		}
7889 		node = element.getElementsByTagName("avatar")[0];
7890 		if (node != null) {
7891 			this.avatar = SDK.innerHTML(node);
7892 		}
7893 		var nodes = element.getElementsByTagName("replies");
7894 		if (nodes != null && nodes.length > 0) {
7895 			this.replies = [];
7896 			for (var index = 0; index < nodes.length; index++) {
7897 				var reply = nodes[index];
7898 				var config = new ForumPostConfig();
7899 				config.parseXML(reply);
7900 				this.replies[replies.length] = (config);
7901 			}
7902 		}
7903 	}
7904 }
7905 ForumPostConfig.prototype = new Config();
7906 ForumPostConfig.prototype.constructor = ForumPostConfig;
7907 ForumPostConfig.constructor = ForumPostConfig;
7908 
7909 /**
7910  * The Instance config object defines the settings for a bot instance.
7911  * It is used to create, edit, and reference a bot.
7912  * It inherits from the WebMediumConfig class.
7913  * @see {@link WebMediumConfig}
7914  * @class
7915  * @property size
7916  * @property allowForking
7917  * @property template
7918  */
7919 function InstanceConfig() {
7920 	/** Read-only : the current size of the bot's knowledge base. **/
7921 	this.size;
7922 	/** Sets if the bot can be forked. */
7923 	this.allowForking;
7924 	/** Sets the name or id of a bot to clone to create a new bot. */
7925 	this.template;
7926 	
7927 	this.type = "instance";
7928 	
7929 	this.credentials = function() {
7930 		var config = new InstanceConfig();
7931 		config.id = this.id;
7932 		return config;
7933 	}
7934 	
7935 	this.toXML = function() {
7936 		var xml = "<instance";
7937 		if (this.allowForking) {
7938 			xml = xml + (" allowForking=\"true\"");
7939 		}
7940 		xml = this.writeWebMediumXML(xml);
7941 		if (this.template != null) {
7942 			xml = xml + ("<template>");
7943 			xml = xml + (this.template);
7944 			xml = xml + ("</template>");
7945 		}
7946 		xml = xml + ("</instance>");
7947 		return xml;
7948 	}
7949 	
7950 	this.parseXML = function(element) {
7951 		this.parseWebMediumXML(element);
7952 		this.allowForking = element.getAttribute("allowForking");
7953 		this.size = element.getAttribute("size");
7954 		
7955 		var node = element.getElementsByTagName("template")[0];
7956 		if (node != null) {
7957 			this.template = SDK.innerHTML(node);
7958 		}
7959 	}
7960 }
7961 InstanceConfig.prototype = new WebMediumConfig();
7962 InstanceConfig.prototype.constructor = InstanceConfig;
7963 InstanceConfig.constructor = InstanceConfig;
7964 
7965 /**
7966  * The Media config object is used the send and retrieve image, video, audio, and file attachments with the server.
7967  * Media and file attachments can be sent linked in chat messages or forum posts.
7968  * It inherits from the Config class.
7969  * @see {@link Config}
7970  * @class
7971  * @property id
7972  * @property name
7973  * @property type
7974  * @property file
7975  * @property key
7976  */
7977 function MediaConfig() {
7978 	this.id;	
7979 	this.name;
7980 	this.type;
7981 	this.file;
7982 	this.key;
7983 	
7984 	this.parseXML = function (element) {		
7985 		this.id = element.getAttribute("id");
7986 		this.name = element.getAttribute("name");
7987 		this.type = element.getAttribute("type");
7988 		this.file = element.getAttribute("file");
7989 		this.key = element.getAttribute("key");
7990 	}
7991 	
7992 	this.toXML = function() {
7993 		var xml = "<media";
7994 		xml = this.writeCredentials(xml);
7995 
7996 		if (this.id != null) {
7997 			xml = xml + (" id=\"" + this.id + "\"");
7998 		}
7999 		if (this.name != null) {
8000 			xml = xml + (" name=\"" + this.name + "\"");
8001 		}
8002 		if (this.file != null) {
8003 			xml = xml + (" file=\"" + this.file + "\"");
8004 		}
8005 		if (this.key != null) {
8006 			xml = xml + (" key=\"" + this.key + "\"");
8007 		}
8008 		
8009 		xml = xml + ("/>");
8010 		return xml;
8011 	}
8012 }
8013 MediaConfig.prototype = new Config();
8014 MediaConfig.prototype.constructor = MediaConfig;
8015 MediaConfig.constructor = MediaConfig;
8016 
8017 /**
8018  * The Voice config object allows the bot's voice to be configured.
8019  * It inherits from the Config class.
8020  * @see {@link Config} Config
8021  * @class
8022  * @property language
8023  * @property pitch
8024  * @property speechRate
8025  */
8026 function VoiceConfig() {
8027 	/** Voice language code (en, fr, en_US, etc.) */
8028 	this.language;
8029 	this.pitch;
8030 	this.speechRate;
8031 	
8032 	this.parseXML = function (element) {		
8033 		this.language = element.getAttribute("language");
8034 		this.pitch = element.getAttribute("pitch");
8035 		this.speechRate = element.getAttribute("speechRate");
8036 	}
8037 
8038 	
8039 	this.toXML = function() {
8040 		var xml = "<voice";
8041 		xml = this.writeCredentials(xml);
8042 
8043 		if (this.language != null) {
8044 			xml = xml + (" language=\"" + this.language + "\"");
8045 		}
8046 		if (this.pitch != null) {
8047 			xml = xml + (" pitch=\"" + this.pitch + "\"");
8048 		}
8049 		if (this.speechRate != null) {
8050 			xml = xml + (" speechRate=\"" + this.speechRate + "\"");
8051 		}
8052 		
8053 		xml = xml + ("/>");
8054 		return xml;
8055 	}
8056 }
8057 VoiceConfig.prototype = new Config();
8058 VoiceConfig.prototype.constructor = VoiceConfig;
8059 VoiceConfig.constructor = VoiceConfig;
8060 
8061 /**
8062  * The Training config object allows new responses to be added to the bot.
8063  * It supports four operations, AddGreeting, RemoveGreeting, AddDefaultResponse, RemoveDefaultResponse, and AddResponse.
8064  * It inherits from the Config class.
8065  * @see {@link Config} Config
8066  * @class
8067  * @property operation
8068  * @property question
8069  * @property response
8070  */
8071 function TrainingConfig() {
8072 	/** Type of response ("Response", "Greeting", "DefaultResponse"). */
8073 	this.operation;
8074 	/** The question phrase or pattern (i.e. "hello", "what is your name", "Pattern:^ help ^"). */
8075 	this.question;
8076 	/** The response phrase or formula (i.e. "Hello there.", "Formula:"My name is {:target}."", "What would you like help with?"). */
8077 	this.response;
8078 	
8079 	this.parseXML = function (element) {		
8080 		this.operation = element.getAttribute("operation");
8081 		var node = element.getElementsByTagName("question")[0];
8082 		if (node != null) {
8083 			this.question = SDK.innerHTML(node);
8084 		}
8085 		node = element.getElementsByTagName("response")[0];
8086 		if (node != null) {
8087 			this.response = SDK.innerHTML(node);
8088 		}
8089 	}
8090 	
8091 	this.toXML = function() {
8092 		var xml = "<training";
8093 		xml = this.writeCredentials(xml);
8094 
8095 		if (this.operation != null) {
8096 			xml = xml + (" operation=\"" + this.operation + "\"");
8097 		}
8098 		xml = xml + (">");
8099 		if (this.question != null) {
8100 			xml = xml + ("<question>");
8101 			xml = xml + (SDK.escapeHTML(this.question));
8102 			xml = xml + ("</question>");
8103 		}
8104 		if (this.response != null) {
8105 			xml = xml + ("<response>");
8106 			xml = xml + (SDK.escapeHTML(this.response));
8107 			xml = xml + ("</response>");
8108 		}
8109 		xml = xml + ("</training>");
8110 		
8111 		xml = xml + ("/>");
8112 		return xml;
8113 	}
8114 }
8115 TrainingConfig.prototype = new Config();
8116 TrainingConfig.prototype.constructor = TrainingConfig;
8117 TrainingConfig.constructor = TrainingConfig;
8118 
8119 /**
8120  * Allow async loading callback.
8121  */
8122 if (typeof SDK_onLoaded !== 'undefined') {
8123 	SDK_onLoaded();
8124 }
8125 
8126 /**
8127  * Allow for botlibre.SDK namespace.
8128  */
8129 var botlibre = {};
8130 botlibre.SDK = {};
8131 for (var attr in SDK) {
8132     botlibre.SDK[attr] = SDK[attr];
8133 }
8134