You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by mj...@apache.org on 2019/01/01 00:29:35 UTC

[29/51] [partial] guacamole-website git commit: Deploy updated/draft documentation for 1.0.0.

http://git-wip-us.apache.org/repos/asf/guacamole-website/blob/8c2fe14e/content/doc/1.0.0/guacamole-common-js/StringReader.js.html
----------------------------------------------------------------------
diff --git a/content/doc/1.0.0/guacamole-common-js/StringReader.js.html b/content/doc/1.0.0/guacamole-common-js/StringReader.js.html
new file mode 100644
index 0000000..3e26f7d
--- /dev/null
+++ b/content/doc/1.0.0/guacamole-common-js/StringReader.js.html
@@ -0,0 +1,227 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>JSDoc: Source: StringReader.js</title>
+
+    <script src="scripts/prettify/prettify.js"> </script>
+    <script src="scripts/prettify/lang-css.js"> </script>
+    <!--[if lt IE 9]>
+      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
+    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
+</head>
+
+<body>
+
+<div id="main">
+
+    <h1 class="page-title">Source: StringReader.js</h1>
+
+    
+
+
+
+    
+    <section>
+        <article>
+            <pre class="prettyprint source linenums"><code>/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * A reader which automatically handles the given input stream, returning
+ * strictly text data. Note that this object will overwrite any installed event
+ * handlers on the given Guacamole.InputStream.
+ * 
+ * @constructor
+ * @param {Guacamole.InputStream} stream The stream that data will be read
+ *                                       from.
+ */
+Guacamole.StringReader = function(stream) {
+
+    /**
+     * Reference to this Guacamole.InputStream.
+     * @private
+     */
+    var guac_reader = this;
+
+    /**
+     * Wrapped Guacamole.ArrayBufferReader.
+     * @private
+     * @type {Guacamole.ArrayBufferReader}
+     */
+    var array_reader = new Guacamole.ArrayBufferReader(stream);
+
+    /**
+     * The number of bytes remaining for the current codepoint.
+     *
+     * @private
+     * @type {Number}
+     */
+    var bytes_remaining = 0;
+
+    /**
+     * The current codepoint value, as calculated from bytes read so far.
+     *
+     * @private
+     * @type {Number}
+     */
+    var codepoint = 0;
+
+    /**
+     * Decodes the given UTF-8 data into a Unicode string. The data may end in
+     * the middle of a multibyte character.
+     * 
+     * @private
+     * @param {ArrayBuffer} buffer Arbitrary UTF-8 data.
+     * @return {String} A decoded Unicode string.
+     */
+    function __decode_utf8(buffer) {
+
+        var text = "";
+
+        var bytes = new Uint8Array(buffer);
+        for (var i=0; i&lt;bytes.length; i++) {
+
+            // Get current byte
+            var value = bytes[i];
+
+            // Start new codepoint if nothing yet read
+            if (bytes_remaining === 0) {
+
+                // 1 byte (0xxxxxxx)
+                if ((value | 0x7F) === 0x7F)
+                    text += String.fromCharCode(value);
+
+                // 2 byte (110xxxxx)
+                else if ((value | 0x1F) === 0xDF) {
+                    codepoint = value &amp; 0x1F;
+                    bytes_remaining = 1;
+                }
+
+                // 3 byte (1110xxxx)
+                else if ((value | 0x0F )=== 0xEF) {
+                    codepoint = value &amp; 0x0F;
+                    bytes_remaining = 2;
+                }
+
+                // 4 byte (11110xxx)
+                else if ((value | 0x07) === 0xF7) {
+                    codepoint = value &amp; 0x07;
+                    bytes_remaining = 3;
+                }
+
+                // Invalid byte
+                else
+                    text += "\uFFFD";
+
+            }
+
+            // Continue existing codepoint (10xxxxxx)
+            else if ((value | 0x3F) === 0xBF) {
+
+                codepoint = (codepoint &lt;&lt; 6) | (value &amp; 0x3F);
+                bytes_remaining--;
+
+                // Write codepoint if finished
+                if (bytes_remaining === 0)
+                    text += String.fromCharCode(codepoint);
+
+            }
+
+            // Invalid byte
+            else {
+                bytes_remaining = 0;
+                text += "\uFFFD";
+            }
+
+        }
+
+        return text;
+
+    }
+
+    // Receive blobs as strings
+    array_reader.ondata = function(buffer) {
+
+        // Decode UTF-8
+        var text = __decode_utf8(buffer);
+
+        // Call handler, if present
+        if (guac_reader.ontext)
+            guac_reader.ontext(text);
+
+    };
+
+    // Simply call onend when end received
+    array_reader.onend = function() {
+        if (guac_reader.onend)
+            guac_reader.onend();
+    };
+
+    /**
+     * Fired once for every blob of text data received.
+     * 
+     * @event
+     * @param {String} text The data packet received.
+     */
+    this.ontext = null;
+
+    /**
+     * Fired once this stream is finished and no further data will be written.
+     * @event
+     */
+    this.onend = null;
+
+};</code></pre>
+        </article>
+    </section>
+
+
+
+
+</div>
+
+<nav>
+    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Client</a></li><li><a href="Guacamole.DataURIReader.html">DataURIReader</a></li><li><a href="Guacamole.Display.html">Display</a></li><li><a href="Guacamole.Display.VisibleLayer.html">VisibleLayer</a></li><li><a href="Guacamole.HTTPTunnel.html">HTTPTunnel</a></li><li><a href="Guacamole.InputSink.html">InputSink</a></li><li><a href="Guacamole.InputStream.html">InputStream</a></li><li><a href="Guacamole.IntegerPool.html">IntegerPool</a></li><l
 i><a href="Guacamole.JSONReader.html">JSONReader</a></li><li><a href="Guacamole.Keyboard.html">Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">ModifierState</a></li><li><a href="Guacamole.Layer.html">Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Pixel</a></li><li><a href="Guacamole.Mouse.html">Mouse</a></li><li><a href="Guacamole.Mouse.State.html">State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Touchscreen</a></li><li><a href="Guacamole.Object.html">Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Layout</a></li><li><a href="Guacamole.OutputStream.html">OutputStream</a></li><li><a href="Guacamole.Parser.html">Parser</a></li><li><a href="Guacamole.RawAudioFormat.html">RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html
 ">RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioRecorder.html">RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Status</a></li><li><a href="Guacamole.StringReader.html">StringReader</a></li><li><a href="Guacamole.StringWriter.html">StringWriter</a></li><li><a href="Guacamole.Tunnel.html">Tunnel</a></li><li><a href="Guacamole.VideoPlayer.html">VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">onerror</a></li><li><a hre
 f="Guacamole.BlobReader.html#event:onend">onend</a></li><li><a href="Guacamole.BlobReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onaudio">onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">onfilesys
 tem</a></li><li><a href="Guacamole.Client.html#event:onname">onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">onpipe</a></li><li><a href="Guacamole.Client.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">onresize</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.InputStream.html#event:onblob">onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">onend</a></li><li><a
  href="Guacamole.JSONReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.html#event:onmouseout">onmouseout</a></li><li><a href="Guacamole.Mouse.html#event:onmouseup">onmouseup</a></li><li><a h
 ref="Guacamole.Object.html#event:onbody">onbody</a></li><li><a href="Guacamole.Object.html#event:onundefine">onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">onpause</a></li><li><a href="Guacamole.SessionRecording
 .html#event:onplay">onplay</a></li><li><a href="Guacamole.SessionRecording.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">onseek</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.StringReader.html#event:onend">onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">onack</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:o
 ninstruction">oninstruction</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onstatechange">onstatechange</a></li></ul><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">AudioContextFactory</a></li></ul>
+</nav>
+
+<br class="clear">
+
+<footer>
+    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Fri Dec 21 2018 13:47:10 GMT-0800 (PST)
+</footer>
+
+<script> prettyPrint(); </script>
+<script src="scripts/linenumber.js"> </script>
+        <!-- Google Analytics -->
+        <script type="text/javascript">
+          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+          })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+          ga('create', 'UA-75289145-1', 'auto');
+          ga('send', 'pageview');
+        </script>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/guacamole-website/blob/8c2fe14e/content/doc/1.0.0/guacamole-common-js/StringWriter.js.html
----------------------------------------------------------------------
diff --git a/content/doc/1.0.0/guacamole-common-js/StringWriter.js.html b/content/doc/1.0.0/guacamole-common-js/StringWriter.js.html
new file mode 100644
index 0000000..b2b9df5
--- /dev/null
+++ b/content/doc/1.0.0/guacamole-common-js/StringWriter.js.html
@@ -0,0 +1,252 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>JSDoc: Source: StringWriter.js</title>
+
+    <script src="scripts/prettify/prettify.js"> </script>
+    <script src="scripts/prettify/lang-css.js"> </script>
+    <!--[if lt IE 9]>
+      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
+    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
+</head>
+
+<body>
+
+<div id="main">
+
+    <h1 class="page-title">Source: StringWriter.js</h1>
+
+    
+
+
+
+    
+    <section>
+        <article>
+            <pre class="prettyprint source linenums"><code>/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * A writer which automatically writes to the given output stream with text
+ * data.
+ * 
+ * @constructor
+ * @param {Guacamole.OutputStream} stream The stream that data will be written
+ *                                        to.
+ */
+Guacamole.StringWriter = function(stream) {
+
+    /**
+     * Reference to this Guacamole.StringWriter.
+     * @private
+     */
+    var guac_writer = this;
+
+    /**
+     * Wrapped Guacamole.ArrayBufferWriter.
+     * @private
+     * @type {Guacamole.ArrayBufferWriter}
+     */
+    var array_writer = new Guacamole.ArrayBufferWriter(stream);
+
+    /**
+     * Internal buffer for UTF-8 output.
+     * @private
+     */
+    var buffer = new Uint8Array(8192);
+
+    /**
+     * The number of bytes currently in the buffer.
+     * @private
+     */
+    var length = 0;
+
+    // Simply call onack for acknowledgements
+    array_writer.onack = function(status) {
+        if (guac_writer.onack)
+            guac_writer.onack(status);
+    };
+
+    /**
+     * Expands the size of the underlying buffer by the given number of bytes,
+     * updating the length appropriately.
+     * 
+     * @private
+     * @param {Number} bytes The number of bytes to add to the underlying
+     *                       buffer.
+     */
+    function __expand(bytes) {
+
+        // Resize buffer if more space needed
+        if (length+bytes >= buffer.length) {
+            var new_buffer = new Uint8Array((length+bytes)*2);
+            new_buffer.set(buffer);
+            buffer = new_buffer;
+        }
+
+        length += bytes;
+
+    }
+
+    /**
+     * Appends a single Unicode character to the current buffer, resizing the
+     * buffer if necessary. The character will be encoded as UTF-8.
+     * 
+     * @private
+     * @param {Number} codepoint The codepoint of the Unicode character to
+     *                           append.
+     */
+    function __append_utf8(codepoint) {
+
+        var mask;
+        var bytes;
+
+        // 1 byte
+        if (codepoint &lt;= 0x7F) {
+            mask = 0x00;
+            bytes = 1;
+        }
+
+        // 2 byte
+        else if (codepoint &lt;= 0x7FF) {
+            mask = 0xC0;
+            bytes = 2;
+        }
+
+        // 3 byte
+        else if (codepoint &lt;= 0xFFFF) {
+            mask = 0xE0;
+            bytes = 3;
+        }
+
+        // 4 byte
+        else if (codepoint &lt;= 0x1FFFFF) {
+            mask = 0xF0;
+            bytes = 4;
+        }
+
+        // If invalid codepoint, append replacement character
+        else {
+            __append_utf8(0xFFFD);
+            return;
+        }
+
+        // Offset buffer by size
+        __expand(bytes);
+        var offset = length - 1;
+
+        // Add trailing bytes, if any
+        for (var i=1; i&lt;bytes; i++) {
+            buffer[offset--] = 0x80 | (codepoint &amp; 0x3F);
+            codepoint >>= 6;
+        }
+
+        // Set initial byte
+        buffer[offset] = mask | codepoint;
+
+    }
+
+    /**
+     * Encodes the given string as UTF-8, returning an ArrayBuffer containing
+     * the resulting bytes.
+     * 
+     * @private
+     * @param {String} text The string to encode as UTF-8.
+     * @return {Uint8Array} The encoded UTF-8 data.
+     */
+    function __encode_utf8(text) {
+
+        // Fill buffer with UTF-8
+        for (var i=0; i&lt;text.length; i++) {
+            var codepoint = text.charCodeAt(i);
+            __append_utf8(codepoint);
+        }
+
+        // Flush buffer
+        if (length > 0) {
+            var out_buffer = buffer.subarray(0, length);
+            length = 0;
+            return out_buffer;
+        }
+
+    }
+
+    /**
+     * Sends the given text.
+     * 
+     * @param {String} text The text to send.
+     */
+    this.sendText = function(text) {
+        if (text.length)
+            array_writer.sendData(__encode_utf8(text));
+    };
+
+    /**
+     * Signals that no further text will be sent, effectively closing the
+     * stream.
+     */
+    this.sendEnd = function() {
+        array_writer.sendEnd();
+    };
+
+    /**
+     * Fired for received data, if acknowledged by the server.
+     * @event
+     * @param {Guacamole.Status} status The status of the operation.
+     */
+    this.onack = null;
+
+};</code></pre>
+        </article>
+    </section>
+
+
+
+
+</div>
+
+<nav>
+    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Client</a></li><li><a href="Guacamole.DataURIReader.html">DataURIReader</a></li><li><a href="Guacamole.Display.html">Display</a></li><li><a href="Guacamole.Display.VisibleLayer.html">VisibleLayer</a></li><li><a href="Guacamole.HTTPTunnel.html">HTTPTunnel</a></li><li><a href="Guacamole.InputSink.html">InputSink</a></li><li><a href="Guacamole.InputStream.html">InputStream</a></li><li><a href="Guacamole.IntegerPool.html">IntegerPool</a></li><l
 i><a href="Guacamole.JSONReader.html">JSONReader</a></li><li><a href="Guacamole.Keyboard.html">Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">ModifierState</a></li><li><a href="Guacamole.Layer.html">Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Pixel</a></li><li><a href="Guacamole.Mouse.html">Mouse</a></li><li><a href="Guacamole.Mouse.State.html">State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Touchscreen</a></li><li><a href="Guacamole.Object.html">Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Layout</a></li><li><a href="Guacamole.OutputStream.html">OutputStream</a></li><li><a href="Guacamole.Parser.html">Parser</a></li><li><a href="Guacamole.RawAudioFormat.html">RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html
 ">RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioRecorder.html">RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Status</a></li><li><a href="Guacamole.StringReader.html">StringReader</a></li><li><a href="Guacamole.StringWriter.html">StringWriter</a></li><li><a href="Guacamole.Tunnel.html">Tunnel</a></li><li><a href="Guacamole.VideoPlayer.html">VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">onerror</a></li><li><a hre
 f="Guacamole.BlobReader.html#event:onend">onend</a></li><li><a href="Guacamole.BlobReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onaudio">onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">onfilesys
 tem</a></li><li><a href="Guacamole.Client.html#event:onname">onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">onpipe</a></li><li><a href="Guacamole.Client.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">onresize</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.InputStream.html#event:onblob">onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">onend</a></li><li><a
  href="Guacamole.JSONReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.html#event:onmouseout">onmouseout</a></li><li><a href="Guacamole.Mouse.html#event:onmouseup">onmouseup</a></li><li><a h
 ref="Guacamole.Object.html#event:onbody">onbody</a></li><li><a href="Guacamole.Object.html#event:onundefine">onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">onpause</a></li><li><a href="Guacamole.SessionRecording
 .html#event:onplay">onplay</a></li><li><a href="Guacamole.SessionRecording.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">onseek</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.StringReader.html#event:onend">onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">onack</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:o
 ninstruction">oninstruction</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onstatechange">onstatechange</a></li></ul><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">AudioContextFactory</a></li></ul>
+</nav>
+
+<br class="clear">
+
+<footer>
+    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Fri Dec 21 2018 13:47:10 GMT-0800 (PST)
+</footer>
+
+<script> prettyPrint(); </script>
+<script src="scripts/linenumber.js"> </script>
+        <!-- Google Analytics -->
+        <script type="text/javascript">
+          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+          })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+          ga('create', 'UA-75289145-1', 'auto');
+          ga('send', 'pageview');
+        </script>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/guacamole-website/blob/8c2fe14e/content/doc/1.0.0/guacamole-common-js/Tunnel.js.html
----------------------------------------------------------------------
diff --git a/content/doc/1.0.0/guacamole-common-js/Tunnel.js.html b/content/doc/1.0.0/guacamole-common-js/Tunnel.js.html
new file mode 100644
index 0000000..f219192
--- /dev/null
+++ b/content/doc/1.0.0/guacamole-common-js/Tunnel.js.html
@@ -0,0 +1,1445 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>JSDoc: Source: Tunnel.js</title>
+
+    <script src="scripts/prettify/prettify.js"> </script>
+    <script src="scripts/prettify/lang-css.js"> </script>
+    <!--[if lt IE 9]>
+      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
+    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
+</head>
+
+<body>
+
+<div id="main">
+
+    <h1 class="page-title">Source: Tunnel.js</h1>
+
+    
+
+
+
+    
+    <section>
+        <article>
+            <pre class="prettyprint source linenums"><code>/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * Core object providing abstract communication for Guacamole. This object
+ * is a null implementation whose functions do nothing. Guacamole applications
+ * should use {@link Guacamole.HTTPTunnel} instead, or implement their own tunnel based
+ * on this one.
+ * 
+ * @constructor
+ * @see Guacamole.HTTPTunnel
+ */
+Guacamole.Tunnel = function() {
+
+    /**
+     * Connect to the tunnel with the given optional data. This data is
+     * typically used for authentication. The format of data accepted is
+     * up to the tunnel implementation.
+     * 
+     * @param {String} data The data to send to the tunnel when connecting.
+     */
+    this.connect = function(data) {};
+    
+    /**
+     * Disconnect from the tunnel.
+     */
+    this.disconnect = function() {};
+    
+    /**
+     * Send the given message through the tunnel to the service on the other
+     * side. All messages are guaranteed to be received in the order sent.
+     * 
+     * @param {...*} elements
+     *     The elements of the message to send to the service on the other side
+     *     of the tunnel.
+     */
+    this.sendMessage = function(elements) {};
+
+    /**
+     * Changes the stored numeric state of this tunnel, firing the onstatechange
+     * event if the new state is different and a handler has been defined.
+     *
+     * @private
+     * @param {Number} state
+     *     The new state of this tunnel.
+     */
+    this.setState = function(state) {
+
+        // Notify only if state changes
+        if (state !== this.state) {
+            this.state = state;
+            if (this.onstatechange)
+                this.onstatechange(state);
+        }
+
+    };
+
+    /**
+     * Returns whether this tunnel is currently connected.
+     *
+     * @returns {Boolean}
+     *     true if this tunnel is currently connected, false otherwise.
+     */
+    this.isConnected = function isConnected() {
+        return this.state === Guacamole.Tunnel.State.OPEN
+            || this.state === Guacamole.Tunnel.State.UNSTABLE;
+    };
+
+    /**
+     * The current state of this tunnel.
+     * 
+     * @type {Number}
+     */
+    this.state = Guacamole.Tunnel.State.CONNECTING;
+
+    /**
+     * The maximum amount of time to wait for data to be received, in
+     * milliseconds. If data is not received within this amount of time,
+     * the tunnel is closed with an error. The default value is 15000.
+     *
+     * @type {Number}
+     */
+    this.receiveTimeout = 15000;
+
+    /**
+     * The amount of time to wait for data to be received before considering
+     * the connection to be unstable, in milliseconds. If data is not received
+     * within this amount of time, the tunnel status is updated to warn that
+     * the connection appears unresponsive and may close. The default value is
+     * 1500.
+     * 
+     * @type {Number}
+     */
+    this.unstableThreshold = 1500;
+
+    /**
+     * The UUID uniquely identifying this tunnel. If not yet known, this will
+     * be null.
+     *
+     * @type {String}
+     */
+    this.uuid = null;
+
+    /**
+     * Fired whenever an error is encountered by the tunnel.
+     * 
+     * @event
+     * @param {Guacamole.Status} status A status object which describes the
+     *                                  error.
+     */
+    this.onerror = null;
+
+    /**
+     * Fired whenever the state of the tunnel changes.
+     * 
+     * @event
+     * @param {Number} state The new state of the client.
+     */
+    this.onstatechange = null;
+
+    /**
+     * Fired once for every complete Guacamole instruction received, in order.
+     * 
+     * @event
+     * @param {String} opcode The Guacamole instruction opcode.
+     * @param {Array} parameters The parameters provided for the instruction,
+     *                           if any.
+     */
+    this.oninstruction = null;
+
+};
+
+/**
+ * The Guacamole protocol instruction opcode reserved for arbitrary internal
+ * use by tunnel implementations. The value of this opcode is guaranteed to be
+ * the empty string (""). Tunnel implementations may use this opcode for any
+ * purpose. It is currently used by the HTTP tunnel to mark the end of the HTTP
+ * response, and by the WebSocket tunnel to transmit the tunnel UUID and send
+ * connection stability test pings/responses.
+ *
+ * @constant
+ * @type {String}
+ */
+Guacamole.Tunnel.INTERNAL_DATA_OPCODE = '';
+
+/**
+ * All possible tunnel states.
+ */
+Guacamole.Tunnel.State = {
+
+    /**
+     * A connection is in pending. It is not yet known whether connection was
+     * successful.
+     * 
+     * @type {Number}
+     */
+    "CONNECTING": 0,
+
+    /**
+     * Connection was successful, and data is being received.
+     * 
+     * @type {Number}
+     */
+    "OPEN": 1,
+
+    /**
+     * The connection is closed. Connection may not have been successful, the
+     * tunnel may have been explicitly closed by either side, or an error may
+     * have occurred.
+     * 
+     * @type {Number}
+     */
+    "CLOSED": 2,
+
+    /**
+     * The connection is open, but communication through the tunnel appears to
+     * be disrupted, and the connection may close as a result.
+     *
+     * @type {Number}
+     */
+    "UNSTABLE" : 3
+
+};
+
+/**
+ * Guacamole Tunnel implemented over HTTP via XMLHttpRequest.
+ * 
+ * @constructor
+ * @augments Guacamole.Tunnel
+ *
+ * @param {String} tunnelURL
+ *     The URL of the HTTP tunneling service.
+ *
+ * @param {Boolean} [crossDomain=false]
+ *     Whether tunnel requests will be cross-domain, and thus must use CORS
+ *     mechanisms and headers. By default, it is assumed that tunnel requests
+ *     will be made to the same domain.
+ *
+ * @param {Object} [extraTunnelHeaders={}]
+ *     Key value pairs containing the header names and values of any additional
+ *     headers to be sent in tunnel requests. By default, no extra headers will
+ *     be added.
+ */
+Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
+
+    /**
+     * Reference to this HTTP tunnel.
+     * @private
+     */
+    var tunnel = this;
+
+    var TUNNEL_CONNECT = tunnelURL + "?connect";
+    var TUNNEL_READ    = tunnelURL + "?read:";
+    var TUNNEL_WRITE   = tunnelURL + "?write:";
+
+    var POLLING_ENABLED     = 1;
+    var POLLING_DISABLED    = 0;
+
+    // Default to polling - will be turned off automatically if not needed
+    var pollingMode = POLLING_ENABLED;
+
+    var sendingMessages = false;
+    var outputMessageBuffer = "";
+
+    // If requests are expected to be cross-domain, the cookie that the HTTP
+    // tunnel depends on will only be sent if withCredentials is true
+    var withCredentials = !!crossDomain;
+
+    /**
+     * The current receive timeout ID, if any.
+     * @private
+     */
+    var receive_timeout = null;
+
+    /**
+     * The current connection stability timeout ID, if any.
+     *
+     * @private
+     * @type {Number}
+     */
+    var unstableTimeout = null;
+
+    /**
+     * The current connection stability test ping interval ID, if any. This
+     * will only be set upon successful connection.
+     *
+     * @private
+     * @type {Number}
+     */
+    var pingInterval = null;
+
+    /**
+     * The number of milliseconds to wait between connection stability test
+     * pings.
+     *
+     * @private
+     * @constant
+     * @type {Number}
+     */
+    var PING_FREQUENCY = 500;
+
+    /**
+     * Additional headers to be sent in tunnel requests. This dictionary can be
+     * populated with key/value header pairs to pass information such as authentication
+     * tokens, etc.
+     *
+     * @private
+     */
+    var extraHeaders = extraTunnelHeaders || {};
+
+    /**
+     * Adds the configured additional headers to the given request.
+     *
+     * @param {XMLHttpRequest} request
+     *     The request where the configured extra headers will be added.
+     *
+     * @param {Object} headers
+     *     The headers to be added to the request.
+     *
+     * @private
+     */
+    function addExtraHeaders(request, headers) {
+        for (var name in headers) {
+            request.setRequestHeader(name, headers[name]);
+        }
+    }
+
+    /**
+     * Initiates a timeout which, if data is not received, causes the tunnel
+     * to close with an error.
+     * 
+     * @private
+     */
+    function reset_timeout() {
+
+        // Get rid of old timeouts (if any)
+        window.clearTimeout(receive_timeout);
+        window.clearTimeout(unstableTimeout);
+
+        // Clear unstable status
+        if (tunnel.state === Guacamole.Tunnel.State.UNSTABLE)
+            tunnel.setState(Guacamole.Tunnel.State.OPEN);
+
+        // Set new timeout for tracking overall connection timeout
+        receive_timeout = window.setTimeout(function () {
+            close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_TIMEOUT, "Server timeout."));
+        }, tunnel.receiveTimeout);
+
+        // Set new timeout for tracking suspected connection instability
+        unstableTimeout = window.setTimeout(function() {
+            tunnel.setState(Guacamole.Tunnel.State.UNSTABLE);
+        }, tunnel.unstableThreshold);
+
+    }
+
+    /**
+     * Closes this tunnel, signaling the given status and corresponding
+     * message, which will be sent to the onerror handler if the status is
+     * an error status.
+     * 
+     * @private
+     * @param {Guacamole.Status} status The status causing the connection to
+     *                                  close;
+     */
+    function close_tunnel(status) {
+
+        // Get rid of old timeouts (if any)
+        window.clearTimeout(receive_timeout);
+        window.clearTimeout(unstableTimeout);
+
+        // Cease connection test pings
+        window.clearInterval(pingInterval);
+
+        // Ignore if already closed
+        if (tunnel.state === Guacamole.Tunnel.State.CLOSED)
+            return;
+
+        // If connection closed abnormally, signal error.
+        if (status.code !== Guacamole.Status.Code.SUCCESS &amp;&amp; tunnel.onerror) {
+
+            // Ignore RESOURCE_NOT_FOUND if we've already connected, as that
+            // only signals end-of-stream for the HTTP tunnel.
+            if (tunnel.state === Guacamole.Tunnel.State.CONNECTING
+                    || status.code !== Guacamole.Status.Code.RESOURCE_NOT_FOUND)
+                tunnel.onerror(status);
+
+        }
+
+        // Reset output message buffer
+        sendingMessages = false;
+
+        // Mark as closed
+        tunnel.setState(Guacamole.Tunnel.State.CLOSED);
+
+    }
+
+
+    this.sendMessage = function() {
+
+        // Do not attempt to send messages if not connected
+        if (!tunnel.isConnected())
+            return;
+
+        // Do not attempt to send empty messages
+        if (arguments.length === 0)
+            return;
+
+        /**
+         * Converts the given value to a length/string pair for use as an
+         * element in a Guacamole instruction.
+         * 
+         * @private
+         * @param value The value to convert.
+         * @return {String} The converted value. 
+         */
+        function getElement(value) {
+            var string = new String(value);
+            return string.length + "." + string; 
+        }
+
+        // Initialized message with first element
+        var message = getElement(arguments[0]);
+
+        // Append remaining elements
+        for (var i=1; i&lt;arguments.length; i++)
+            message += "," + getElement(arguments[i]);
+
+        // Final terminator
+        message += ";";
+
+        // Add message to buffer
+        outputMessageBuffer += message;
+
+        // Send if not currently sending
+        if (!sendingMessages)
+            sendPendingMessages();
+
+    };
+
+    function sendPendingMessages() {
+
+        // Do not attempt to send messages if not connected
+        if (!tunnel.isConnected())
+            return;
+
+        if (outputMessageBuffer.length > 0) {
+
+            sendingMessages = true;
+
+            var message_xmlhttprequest = new XMLHttpRequest();
+            message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel.uuid);
+            message_xmlhttprequest.withCredentials = withCredentials;
+            addExtraHeaders(message_xmlhttprequest, extraHeaders);
+            message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream");
+
+            // Once response received, send next queued event.
+            message_xmlhttprequest.onreadystatechange = function() {
+                if (message_xmlhttprequest.readyState === 4) {
+
+                    reset_timeout();
+
+                    // If an error occurs during send, handle it
+                    if (message_xmlhttprequest.status !== 200)
+                        handleHTTPTunnelError(message_xmlhttprequest);
+
+                    // Otherwise, continue the send loop
+                    else
+                        sendPendingMessages();
+
+                }
+            };
+
+            message_xmlhttprequest.send(outputMessageBuffer);
+            outputMessageBuffer = ""; // Clear buffer
+
+        }
+        else
+            sendingMessages = false;
+
+    }
+
+    function handleHTTPTunnelError(xmlhttprequest) {
+
+        // Pull status code directly from headers provided by Guacamole
+        var code = parseInt(xmlhttprequest.getResponseHeader("Guacamole-Status-Code"));
+        if (code) {
+            var message = xmlhttprequest.getResponseHeader("Guacamole-Error-Message");
+            close_tunnel(new Guacamole.Status(code, message));
+        }
+
+        // Failing that, derive a Guacamole status code from the HTTP status
+        // code provided by the browser
+        else if (xmlhttprequest.status)
+            close_tunnel(new Guacamole.Status(
+                Guacamole.Status.Code.fromHTTPCode(xmlhttprequest.status),
+                    xmlhttprequest.statusText));
+
+        // Otherwise, assume server is unreachable
+        else
+            close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND));
+
+    }
+
+    function handleResponse(xmlhttprequest) {
+
+        var interval = null;
+        var nextRequest = null;
+
+        var dataUpdateEvents = 0;
+
+        // The location of the last element's terminator
+        var elementEnd = -1;
+
+        // Where to start the next length search or the next element
+        var startIndex = 0;
+
+        // Parsed elements
+        var elements = new Array();
+
+        function parseResponse() {
+
+            // Do not handle responses if not connected
+            if (!tunnel.isConnected()) {
+                
+                // Clean up interval if polling
+                if (interval !== null)
+                    clearInterval(interval);
+                
+                return;
+            }
+
+            // Do not parse response yet if not ready
+            if (xmlhttprequest.readyState &lt; 2) return;
+
+            // Attempt to read status
+            var status;
+            try { status = xmlhttprequest.status; }
+
+            // If status could not be read, assume successful.
+            catch (e) { status = 200; }
+
+            // Start next request as soon as possible IF request was successful
+            if (!nextRequest &amp;&amp; status === 200)
+                nextRequest = makeRequest();
+
+            // Parse stream when data is received and when complete.
+            if (xmlhttprequest.readyState === 3 ||
+                xmlhttprequest.readyState === 4) {
+
+                reset_timeout();
+
+                // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data)
+                if (pollingMode === POLLING_ENABLED) {
+                    if (xmlhttprequest.readyState === 3 &amp;&amp; !interval)
+                        interval = setInterval(parseResponse, 30);
+                    else if (xmlhttprequest.readyState === 4 &amp;&amp; interval)
+                        clearInterval(interval);
+                }
+
+                // If canceled, stop transfer
+                if (xmlhttprequest.status === 0) {
+                    tunnel.disconnect();
+                    return;
+                }
+
+                // Halt on error during request
+                else if (xmlhttprequest.status !== 200) {
+                    handleHTTPTunnelError(xmlhttprequest);
+                    return;
+                }
+
+                // Attempt to read in-progress data
+                var current;
+                try { current = xmlhttprequest.responseText; }
+
+                // Do not attempt to parse if data could not be read
+                catch (e) { return; }
+
+                // While search is within currently received data
+                while (elementEnd &lt; current.length) {
+
+                    // If we are waiting for element data
+                    if (elementEnd >= startIndex) {
+
+                        // We now have enough data for the element. Parse.
+                        var element = current.substring(startIndex, elementEnd);
+                        var terminator = current.substring(elementEnd, elementEnd+1);
+
+                        // Add element to array
+                        elements.push(element);
+
+                        // If last element, handle instruction
+                        if (terminator === ";") {
+
+                            // Get opcode
+                            var opcode = elements.shift();
+
+                            // Call instruction handler.
+                            if (tunnel.oninstruction)
+                                tunnel.oninstruction(opcode, elements);
+
+                            // Clear elements
+                            elements.length = 0;
+
+                        }
+
+                        // Start searching for length at character after
+                        // element terminator
+                        startIndex = elementEnd + 1;
+
+                    }
+
+                    // Search for end of length
+                    var lengthEnd = current.indexOf(".", startIndex);
+                    if (lengthEnd !== -1) {
+
+                        // Parse length
+                        var length = parseInt(current.substring(elementEnd+1, lengthEnd));
+
+                        // If we're done parsing, handle the next response.
+                        if (length === 0) {
+
+                            // Clean up interval if polling
+                            if (interval)
+                                clearInterval(interval);
+                           
+                            // Clean up object
+                            xmlhttprequest.onreadystatechange = null;
+                            xmlhttprequest.abort();
+
+                            // Start handling next request
+                            if (nextRequest)
+                                handleResponse(nextRequest);
+
+                            // Done parsing
+                            break;
+
+                        }
+
+                        // Calculate start of element
+                        startIndex = lengthEnd + 1;
+
+                        // Calculate location of element terminator
+                        elementEnd = startIndex + length;
+
+                    }
+                    
+                    // If no period yet, continue search when more data
+                    // is received
+                    else {
+                        startIndex = current.length;
+                        break;
+                    }
+
+                } // end parse loop
+
+            }
+
+        }
+
+        // If response polling enabled, attempt to detect if still
+        // necessary (via wrapping parseResponse())
+        if (pollingMode === POLLING_ENABLED) {
+            xmlhttprequest.onreadystatechange = function() {
+
+                // If we receive two or more readyState==3 events,
+                // there is no need to poll.
+                if (xmlhttprequest.readyState === 3) {
+                    dataUpdateEvents++;
+                    if (dataUpdateEvents >= 2) {
+                        pollingMode = POLLING_DISABLED;
+                        xmlhttprequest.onreadystatechange = parseResponse;
+                    }
+                }
+
+                parseResponse();
+            };
+        }
+
+        // Otherwise, just parse
+        else
+            xmlhttprequest.onreadystatechange = parseResponse;
+
+        parseResponse();
+
+    }
+
+    /**
+     * Arbitrary integer, unique for each tunnel read request.
+     * @private
+     */
+    var request_id = 0;
+
+    function makeRequest() {
+
+        // Make request, increment request ID
+        var xmlhttprequest = new XMLHttpRequest();
+        xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + (request_id++));
+        xmlhttprequest.withCredentials = withCredentials;
+        addExtraHeaders(xmlhttprequest, extraHeaders);
+        xmlhttprequest.send(null);
+
+        return xmlhttprequest;
+
+    }
+
+    this.connect = function(data) {
+
+        // Start waiting for connect
+        reset_timeout();
+
+        // Mark the tunnel as connecting
+        tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
+
+        // Start tunnel and connect
+        var connect_xmlhttprequest = new XMLHttpRequest();
+        connect_xmlhttprequest.onreadystatechange = function() {
+
+            if (connect_xmlhttprequest.readyState !== 4)
+                return;
+
+            // If failure, throw error
+            if (connect_xmlhttprequest.status !== 200) {
+                handleHTTPTunnelError(connect_xmlhttprequest);
+                return;
+            }
+
+            reset_timeout();
+
+            // Get UUID from response
+            tunnel.uuid = connect_xmlhttprequest.responseText;
+
+            // Mark as open
+            tunnel.setState(Guacamole.Tunnel.State.OPEN);
+
+            // Ping tunnel endpoint regularly to test connection stability
+            pingInterval = setInterval(function sendPing() {
+                tunnel.sendMessage("nop");
+            }, PING_FREQUENCY);
+
+            // Start reading data
+            handleResponse(makeRequest());
+
+        };
+
+        connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, true);
+        connect_xmlhttprequest.withCredentials = withCredentials;
+        addExtraHeaders(connect_xmlhttprequest, extraHeaders);
+        connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
+        connect_xmlhttprequest.send(data);
+
+    };
+
+    this.disconnect = function() {
+        close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed."));
+    };
+
+};
+
+Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel();
+
+/**
+ * Guacamole Tunnel implemented over WebSocket via XMLHttpRequest.
+ * 
+ * @constructor
+ * @augments Guacamole.Tunnel
+ * @param {String} tunnelURL The URL of the WebSocket tunneling service.
+ */
+Guacamole.WebSocketTunnel = function(tunnelURL) {
+
+    /**
+     * Reference to this WebSocket tunnel.
+     * @private
+     */
+    var tunnel = this;
+
+    /**
+     * The WebSocket used by this tunnel.
+     * @private
+     */
+    var socket = null;
+
+    /**
+     * The current receive timeout ID, if any.
+     * @private
+     */
+    var receive_timeout = null;
+
+    /**
+     * The current connection stability timeout ID, if any.
+     *
+     * @private
+     * @type {Number}
+     */
+    var unstableTimeout = null;
+
+    /**
+     * The current connection stability test ping interval ID, if any. This
+     * will only be set upon successful connection.
+     *
+     * @private
+     * @type {Number}
+     */
+    var pingInterval = null;
+
+    /**
+     * The WebSocket protocol corresponding to the protocol used for the current
+     * location.
+     * @private
+     */
+    var ws_protocol = {
+        "http:":  "ws:",
+        "https:": "wss:"
+    };
+
+    /**
+     * The number of milliseconds to wait between connection stability test
+     * pings.
+     *
+     * @private
+     * @constant
+     * @type {Number}
+     */
+    var PING_FREQUENCY = 500;
+
+    // Transform current URL to WebSocket URL
+
+    // If not already a websocket URL
+    if (   tunnelURL.substring(0, 3) !== "ws:"
+        &amp;&amp; tunnelURL.substring(0, 4) !== "wss:") {
+
+        var protocol = ws_protocol[window.location.protocol];
+
+        // If absolute URL, convert to absolute WS URL
+        if (tunnelURL.substring(0, 1) === "/")
+            tunnelURL =
+                protocol
+                + "//" + window.location.host
+                + tunnelURL;
+
+        // Otherwise, construct absolute from relative URL
+        else {
+
+            // Get path from pathname
+            var slash = window.location.pathname.lastIndexOf("/");
+            var path  = window.location.pathname.substring(0, slash + 1);
+
+            // Construct absolute URL
+            tunnelURL =
+                protocol
+                + "//" + window.location.host
+                + path
+                + tunnelURL;
+
+        }
+
+    }
+
+    /**
+     * Initiates a timeout which, if data is not received, causes the tunnel
+     * to close with an error.
+     * 
+     * @private
+     */
+    function reset_timeout() {
+
+        // Get rid of old timeouts (if any)
+        window.clearTimeout(receive_timeout);
+        window.clearTimeout(unstableTimeout);
+
+        // Clear unstable status
+        if (tunnel.state === Guacamole.Tunnel.State.UNSTABLE)
+            tunnel.setState(Guacamole.Tunnel.State.OPEN);
+
+        // Set new timeout for tracking overall connection timeout
+        receive_timeout = window.setTimeout(function () {
+            close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_TIMEOUT, "Server timeout."));
+        }, tunnel.receiveTimeout);
+
+        // Set new timeout for tracking suspected connection instability
+        unstableTimeout = window.setTimeout(function() {
+            tunnel.setState(Guacamole.Tunnel.State.UNSTABLE);
+        }, tunnel.unstableThreshold);
+
+    }
+
+    /**
+     * Closes this tunnel, signaling the given status and corresponding
+     * message, which will be sent to the onerror handler if the status is
+     * an error status.
+     * 
+     * @private
+     * @param {Guacamole.Status} status The status causing the connection to
+     *                                  close;
+     */
+    function close_tunnel(status) {
+
+        // Get rid of old timeouts (if any)
+        window.clearTimeout(receive_timeout);
+        window.clearTimeout(unstableTimeout);
+
+        // Cease connection test pings
+        window.clearInterval(pingInterval);
+
+        // Ignore if already closed
+        if (tunnel.state === Guacamole.Tunnel.State.CLOSED)
+            return;
+
+        // If connection closed abnormally, signal error.
+        if (status.code !== Guacamole.Status.Code.SUCCESS &amp;&amp; tunnel.onerror)
+            tunnel.onerror(status);
+
+        // Mark as closed
+        tunnel.setState(Guacamole.Tunnel.State.CLOSED);
+
+        socket.close();
+
+    }
+
+    this.sendMessage = function(elements) {
+
+        // Do not attempt to send messages if not connected
+        if (!tunnel.isConnected())
+            return;
+
+        // Do not attempt to send empty messages
+        if (arguments.length === 0)
+            return;
+
+        /**
+         * Converts the given value to a length/string pair for use as an
+         * element in a Guacamole instruction.
+         * 
+         * @private
+         * @param value The value to convert.
+         * @return {String} The converted value. 
+         */
+        function getElement(value) {
+            var string = new String(value);
+            return string.length + "." + string; 
+        }
+
+        // Initialized message with first element
+        var message = getElement(arguments[0]);
+
+        // Append remaining elements
+        for (var i=1; i&lt;arguments.length; i++)
+            message += "," + getElement(arguments[i]);
+
+        // Final terminator
+        message += ";";
+
+        socket.send(message);
+
+    };
+
+    this.connect = function(data) {
+
+        reset_timeout();
+
+        // Mark the tunnel as connecting
+        tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
+
+        // Connect socket
+        socket = new WebSocket(tunnelURL + "?" + data, "guacamole");
+
+        socket.onopen = function(event) {
+            reset_timeout();
+
+            // Ping tunnel endpoint regularly to test connection stability
+            pingInterval = setInterval(function sendPing() {
+                tunnel.sendMessage(Guacamole.Tunnel.INTERNAL_DATA_OPCODE,
+                    "ping", new Date().getTime());
+            }, PING_FREQUENCY);
+
+        };
+
+        socket.onclose = function(event) {
+
+            // Pull status code directly from closure reason provided by Guacamole
+            if (event.reason)
+                close_tunnel(new Guacamole.Status(parseInt(event.reason), event.reason));
+
+            // Failing that, derive a Guacamole status code from the WebSocket
+            // status code provided by the browser
+            else if (event.code)
+                close_tunnel(new Guacamole.Status(Guacamole.Status.Code.fromWebSocketCode(event.code)));
+
+            // Otherwise, assume server is unreachable
+            else
+                close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND));
+
+        };
+        
+        socket.onmessage = function(event) {
+
+            reset_timeout();
+
+            var message = event.data;
+            var startIndex = 0;
+            var elementEnd;
+
+            var elements = [];
+
+            do {
+
+                // Search for end of length
+                var lengthEnd = message.indexOf(".", startIndex);
+                if (lengthEnd !== -1) {
+
+                    // Parse length
+                    var length = parseInt(message.substring(elementEnd+1, lengthEnd));
+
+                    // Calculate start of element
+                    startIndex = lengthEnd + 1;
+
+                    // Calculate location of element terminator
+                    elementEnd = startIndex + length;
+
+                }
+                
+                // If no period, incomplete instruction.
+                else
+                    close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, "Incomplete instruction."));
+
+                // We now have enough data for the element. Parse.
+                var element = message.substring(startIndex, elementEnd);
+                var terminator = message.substring(elementEnd, elementEnd+1);
+
+                // Add element to array
+                elements.push(element);
+
+                // If last element, handle instruction
+                if (terminator === ";") {
+
+                    // Get opcode
+                    var opcode = elements.shift();
+
+                    // Update state and UUID when first instruction received
+                    if (tunnel.state === Guacamole.Tunnel.State.CONNECTING) {
+
+                        // Associate tunnel UUID if received
+                        if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE)
+                            tunnel.uuid = elements[0];
+
+                        // Tunnel is now open and UUID is available
+                        tunnel.setState(Guacamole.Tunnel.State.OPEN);
+
+                    }
+
+                    // Call instruction handler.
+                    if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE &amp;&amp; tunnel.oninstruction)
+                        tunnel.oninstruction(opcode, elements);
+
+                    // Clear elements
+                    elements.length = 0;
+
+                }
+
+                // Start searching for length at character after
+                // element terminator
+                startIndex = elementEnd + 1;
+
+            } while (startIndex &lt; message.length);
+
+        };
+
+    };
+
+    this.disconnect = function() {
+        close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed."));
+    };
+
+};
+
+Guacamole.WebSocketTunnel.prototype = new Guacamole.Tunnel();
+
+/**
+ * Guacamole Tunnel which cycles between all specified tunnels until
+ * no tunnels are left. Another tunnel is used if an error occurs but
+ * no instructions have been received. If an instruction has been
+ * received, or no tunnels remain, the error is passed directly out
+ * through the onerror handler (if defined).
+ * 
+ * @constructor
+ * @augments Guacamole.Tunnel
+ * @param {...*} tunnelChain
+ *     The tunnels to use, in order of priority.
+ */
+Guacamole.ChainedTunnel = function(tunnelChain) {
+
+    /**
+     * Reference to this chained tunnel.
+     * @private
+     */
+    var chained_tunnel = this;
+
+    /**
+     * Data passed in via connect(), to be used for
+     * wrapped calls to other tunnels' connect() functions.
+     * @private
+     */
+    var connect_data;
+
+    /**
+     * Array of all tunnels passed to this ChainedTunnel through the
+     * constructor arguments.
+     * @private
+     */
+    var tunnels = [];
+
+    /**
+     * The tunnel committed via commit_tunnel(), if any, or null if no tunnel
+     * has yet been committed.
+     *
+     * @private
+     * @type {Guacamole.Tunnel}
+     */
+    var committedTunnel = null;
+
+    // Load all tunnels into array
+    for (var i=0; i&lt;arguments.length; i++)
+        tunnels.push(arguments[i]);
+
+    /**
+     * Sets the current tunnel.
+     * 
+     * @private
+     * @param {Guacamole.Tunnel} tunnel The tunnel to set as the current tunnel.
+     */
+    function attach(tunnel) {
+
+        // Set own functions to tunnel's functions
+        chained_tunnel.disconnect  = tunnel.disconnect;
+        chained_tunnel.sendMessage = tunnel.sendMessage;
+
+        /**
+         * Fails the currently-attached tunnel, attaching a new tunnel if
+         * possible.
+         *
+         * @private
+         * @param {Guacamole.Status} [status]
+         *     An object representing the failure that occured in the
+         *     currently-attached tunnel, if known.
+         *
+         * @return {Guacamole.Tunnel}
+         *     The next tunnel, or null if there are no more tunnels to try or
+         *     if no more tunnels should be tried.
+         */
+        var failTunnel = function failTunnel(status) {
+
+            // Do not attempt to continue using next tunnel on server timeout
+            if (status &amp;&amp; status.code === Guacamole.Status.Code.UPSTREAM_TIMEOUT) {
+                tunnels = [];
+                return null;
+            }
+
+            // Get next tunnel
+            var next_tunnel = tunnels.shift();
+
+            // If there IS a next tunnel, try using it.
+            if (next_tunnel) {
+                tunnel.onerror = null;
+                tunnel.oninstruction = null;
+                tunnel.onstatechange = null;
+                attach(next_tunnel);
+            }
+
+            return next_tunnel;
+
+        };
+
+        /**
+         * Use the current tunnel from this point forward. Do not try any more
+         * tunnels, even if the current tunnel fails.
+         * 
+         * @private
+         */
+        function commit_tunnel() {
+            tunnel.onstatechange = chained_tunnel.onstatechange;
+            tunnel.oninstruction = chained_tunnel.oninstruction;
+            tunnel.onerror = chained_tunnel.onerror;
+            chained_tunnel.uuid = tunnel.uuid;
+            committedTunnel = tunnel;
+        }
+
+        // Wrap own onstatechange within current tunnel
+        tunnel.onstatechange = function(state) {
+
+            switch (state) {
+
+                // If open, use this tunnel from this point forward.
+                case Guacamole.Tunnel.State.OPEN:
+                    commit_tunnel();
+                    if (chained_tunnel.onstatechange)
+                        chained_tunnel.onstatechange(state);
+                    break;
+
+                // If closed, mark failure, attempt next tunnel
+                case Guacamole.Tunnel.State.CLOSED:
+                    if (!failTunnel() &amp;&amp; chained_tunnel.onstatechange)
+                        chained_tunnel.onstatechange(state);
+                    break;
+                
+            }
+
+        };
+
+        // Wrap own oninstruction within current tunnel
+        tunnel.oninstruction = function(opcode, elements) {
+
+            // Accept current tunnel
+            commit_tunnel();
+
+            // Invoke handler
+            if (chained_tunnel.oninstruction)
+                chained_tunnel.oninstruction(opcode, elements);
+
+        };
+
+        // Attach next tunnel on error
+        tunnel.onerror = function(status) {
+
+            // Mark failure, attempt next tunnel
+            if (!failTunnel(status) &amp;&amp; chained_tunnel.onerror)
+                chained_tunnel.onerror(status);
+
+        };
+
+        // Attempt connection
+        tunnel.connect(connect_data);
+        
+    }
+
+    this.connect = function(data) {
+       
+        // Remember connect data
+        connect_data = data;
+
+        // Get committed tunnel if exists or the first tunnel on the list
+        var next_tunnel = committedTunnel ? committedTunnel : tunnels.shift();
+
+        // Attach first tunnel
+        if (next_tunnel)
+            attach(next_tunnel);
+
+        // If there IS no first tunnel, error
+        else if (chained_tunnel.onerror)
+            chained_tunnel.onerror(Guacamole.Status.Code.SERVER_ERROR, "No tunnels to try.");
+
+    };
+    
+};
+
+Guacamole.ChainedTunnel.prototype = new Guacamole.Tunnel();
+
+/**
+ * Guacamole Tunnel which replays a Guacamole protocol dump from a static file
+ * received via HTTP. Instructions within the file are parsed and handled as
+ * quickly as possible, while the file is being downloaded.
+ *
+ * @constructor
+ * @augments Guacamole.Tunnel
+ * @param {String} url
+ *     The URL of a Guacamole protocol dump.
+ *
+ * @param {Boolean} [crossDomain=false]
+ *     Whether tunnel requests will be cross-domain, and thus must use CORS
+ *     mechanisms and headers. By default, it is assumed that tunnel requests
+ *     will be made to the same domain.
+ *
+ * @param {Object} [extraTunnelHeaders={}]
+ *     Key value pairs containing the header names and values of any additional
+ *     headers to be sent in tunnel requests. By default, no extra headers will
+ *     be added.
+ */
+Guacamole.StaticHTTPTunnel = function StaticHTTPTunnel(url, crossDomain, extraTunnelHeaders) {
+
+    /**
+     * Reference to this Guacamole.StaticHTTPTunnel.
+     *
+     * @private
+     */
+    var tunnel = this;
+
+    /**
+     * The current, in-progress HTTP request. If no request is currently in
+     * progress, this will be null.
+     *
+     * @private
+     * @type {XMLHttpRequest}
+     */
+    var xhr = null;
+
+    /**
+     * Additional headers to be sent in tunnel requests. This dictionary can be
+     * populated with key/value header pairs to pass information such as authentication
+     * tokens, etc.
+     *
+     * @private
+     */
+    var extraHeaders = extraTunnelHeaders || {};
+
+    /**
+     * Adds the configured additional headers to the given request.
+     *
+     * @param {XMLHttpRequest} request
+     *     The request where the configured extra headers will be added.
+     *
+     * @param {Object} headers
+     *     The headers to be added to the request.
+     *
+     * @private
+     */
+    function addExtraHeaders(request, headers) {
+        for (var name in headers) {
+            request.setRequestHeader(name, headers[name]);
+        }
+    }
+
+    this.sendMessage = function sendMessage(elements) {
+        // Do nothing
+    };
+
+    this.connect = function connect(data) {
+
+        // Ensure any existing connection is killed
+        tunnel.disconnect();
+
+        // Connection is now starting
+        tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
+
+        // Start a new connection
+        xhr = new XMLHttpRequest();
+        xhr.open('GET', url);
+        xhr.withCredentials = !!crossDomain;
+        addExtraHeaders(xhr, extraHeaders);
+        xhr.responseType = 'text';
+        xhr.send(null);
+
+        var offset = 0;
+
+        // Create Guacamole protocol parser specifically for this connection
+        var parser = new Guacamole.Parser();
+
+        // Invoke tunnel's oninstruction handler for each parsed instruction
+        parser.oninstruction = function instructionReceived(opcode, args) {
+            if (tunnel.oninstruction)
+                tunnel.oninstruction(opcode, args);
+        };
+
+        // Continuously parse received data
+        xhr.onreadystatechange = function readyStateChanged() {
+
+            // Parse while data is being received
+            if (xhr.readyState === 3 || xhr.readyState === 4) {
+
+                // Connection is open
+                tunnel.setState(Guacamole.Tunnel.State.OPEN);
+
+                var buffer = xhr.responseText;
+                var length = buffer.length;
+
+                // Parse only the portion of data which is newly received
+                if (offset &lt; length) {
+                    parser.receive(buffer.substring(offset));
+                    offset = length;
+                }
+
+            }
+
+            // Clean up and close when done
+            if (xhr.readyState === 4)
+                tunnel.disconnect();
+
+        };
+
+        // Reset state and close upon error
+        xhr.onerror = function httpError() {
+
+            // Fail if file could not be downloaded via HTTP
+            if (tunnel.onerror)
+                tunnel.onerror(new Guacamole.Status(
+                    Guacamole.Status.Code.fromHTTPCode(xhr.status), xhr.statusText));
+
+            tunnel.disconnect();
+        };
+
+    };
+
+    this.disconnect = function disconnect() {
+
+        // Abort and dispose of XHR if a request is in progress
+        if (xhr) {
+            xhr.abort();
+            xhr = null;
+        }
+
+        // Connection is now closed
+        tunnel.setState(Guacamole.Tunnel.State.CLOSED);
+
+    };
+
+};
+
+Guacamole.StaticHTTPTunnel.prototype = new Guacamole.Tunnel();
+</code></pre>
+        </article>
+    </section>
+
+
+
+
+</div>
+
+<nav>
+    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Client</a></li><li><a href="Guacamole.DataURIReader.html">DataURIReader</a></li><li><a href="Guacamole.Display.html">Display</a></li><li><a href="Guacamole.Display.VisibleLayer.html">VisibleLayer</a></li><li><a href="Guacamole.HTTPTunnel.html">HTTPTunnel</a></li><li><a href="Guacamole.InputSink.html">InputSink</a></li><li><a href="Guacamole.InputStream.html">InputStream</a></li><li><a href="Guacamole.IntegerPool.html">IntegerPool</a></li><l
 i><a href="Guacamole.JSONReader.html">JSONReader</a></li><li><a href="Guacamole.Keyboard.html">Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">ModifierState</a></li><li><a href="Guacamole.Layer.html">Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Pixel</a></li><li><a href="Guacamole.Mouse.html">Mouse</a></li><li><a href="Guacamole.Mouse.State.html">State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Touchscreen</a></li><li><a href="Guacamole.Object.html">Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Layout</a></li><li><a href="Guacamole.OutputStream.html">OutputStream</a></li><li><a href="Guacamole.Parser.html">Parser</a></li><li><a href="Guacamole.RawAudioFormat.html">RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html
 ">RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioRecorder.html">RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Status</a></li><li><a href="Guacamole.StringReader.html">StringReader</a></li><li><a href="Guacamole.StringWriter.html">StringWriter</a></li><li><a href="Guacamole.Tunnel.html">Tunnel</a></li><li><a href="Guacamole.VideoPlayer.html">VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">onerror</a></li><li><a hre
 f="Guacamole.BlobReader.html#event:onend">onend</a></li><li><a href="Guacamole.BlobReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onaudio">onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">onfilesys
 tem</a></li><li><a href="Guacamole.Client.html#event:onname">onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">onpipe</a></li><li><a href="Guacamole.Client.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">onresize</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.InputStream.html#event:onblob">onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">onend</a></li><li><a
  href="Guacamole.JSONReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.html#event:onmouseout">onmouseout</a></li><li><a href="Guacamole.Mouse.html#event:onmouseup">onmouseup</a></li><li><a h
 ref="Guacamole.Object.html#event:onbody">onbody</a></li><li><a href="Guacamole.Object.html#event:onundefine">onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">onpause</a></li><li><a href="Guacamole.SessionRecording
 .html#event:onplay">onplay</a></li><li><a href="Guacamole.SessionRecording.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">onseek</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.StringReader.html#event:onend">onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">onack</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:o
 ninstruction">oninstruction</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onstatechange">onstatechange</a></li></ul><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">AudioContextFactory</a></li></ul>
+</nav>
+
+<br class="clear">
+
+<footer>
+    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Fri Dec 21 2018 13:47:10 GMT-0800 (PST)
+</footer>
+
+<script> prettyPrint(); </script>
+<script src="scripts/linenumber.js"> </script>
+        <!-- Google Analytics -->
+        <script type="text/javascript">
+          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+          })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+          ga('create', 'UA-75289145-1', 'auto');
+          ga('send', 'pageview');
+        </script>
+</body>
+</html>