mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
810 lines
24 KiB
JavaScript
810 lines
24 KiB
JavaScript
/*
|
|
* Copyright (C) 2013 Glyptodon LLC
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
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) {};
|
|
|
|
/**
|
|
* Fired whenever an error is encountered by the tunnel.
|
|
*
|
|
* @event
|
|
* @param {String} message A human-readable description of the error that
|
|
* occurred.
|
|
*/
|
|
this.onerror = 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;
|
|
|
|
};
|
|
|
|
/**
|
|
* Guacamole Tunnel implemented over HTTP via XMLHttpRequest.
|
|
*
|
|
* @constructor
|
|
* @augments Guacamole.Tunnel
|
|
* @param {String} tunnelURL The URL of the HTTP tunneling service.
|
|
*/
|
|
Guacamole.HTTPTunnel = function(tunnelURL) {
|
|
|
|
/**
|
|
* Reference to this HTTP tunnel.
|
|
* @private
|
|
*/
|
|
var tunnel = this;
|
|
|
|
var tunnel_uuid;
|
|
|
|
var TUNNEL_CONNECT = tunnelURL + "?connect";
|
|
var TUNNEL_READ = tunnelURL + "?read:";
|
|
var TUNNEL_WRITE = tunnelURL + "?write:";
|
|
|
|
var STATE_IDLE = 0;
|
|
var STATE_CONNECTED = 1;
|
|
var STATE_DISCONNECTED = 2;
|
|
|
|
var currentState = STATE_IDLE;
|
|
|
|
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 = "";
|
|
|
|
this.sendMessage = function() {
|
|
|
|
// Do not attempt to send messages if not connected
|
|
if (currentState != STATE_CONNECTED)
|
|
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<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() {
|
|
|
|
if (outputMessageBuffer.length > 0) {
|
|
|
|
sendingMessages = true;
|
|
|
|
var message_xmlhttprequest = new XMLHttpRequest();
|
|
message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid);
|
|
message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
|
|
|
// Once response received, send next queued event.
|
|
message_xmlhttprequest.onreadystatechange = function() {
|
|
if (message_xmlhttprequest.readyState == 4) {
|
|
|
|
// 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 getHTTPTunnelErrorMessage(xmlhttprequest) {
|
|
|
|
var status = xmlhttprequest.status;
|
|
|
|
// Special cases
|
|
if (status == 0) return "Disconnected";
|
|
if (status == 200) return "Success";
|
|
if (status == 403) return "Unauthorized";
|
|
if (status == 404) return "Connection closed"; /* While it may be more
|
|
* accurate to say the
|
|
* connection does not
|
|
* exist, it is confusing
|
|
* to the user.
|
|
*
|
|
* In general, this error
|
|
* will only happen when
|
|
* the tunnel does not
|
|
* exist, which happens
|
|
* after the connection
|
|
* is closed and the
|
|
* tunnel is detached.
|
|
*/
|
|
// Internal server errors
|
|
if (status >= 500 && status <= 599) return "Server error";
|
|
|
|
// Otherwise, unknown
|
|
return "Unknown error";
|
|
|
|
}
|
|
|
|
function handleHTTPTunnelError(xmlhttprequest) {
|
|
|
|
// Get error message
|
|
var message = getHTTPTunnelErrorMessage(xmlhttprequest);
|
|
|
|
// Call error handler
|
|
if (tunnel.onerror) tunnel.onerror(message);
|
|
|
|
// Finish
|
|
tunnel.disconnect();
|
|
|
|
}
|
|
|
|
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 (currentState != STATE_CONNECTED) {
|
|
|
|
// Clean up interval if polling
|
|
if (interval != null)
|
|
clearInterval(interval);
|
|
|
|
return;
|
|
}
|
|
|
|
// Do not parse response yet if not ready
|
|
if (xmlhttprequest.readyState < 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 == null && status == 200)
|
|
nextRequest = makeRequest();
|
|
|
|
// Parse stream when data is received and when complete.
|
|
if (xmlhttprequest.readyState == 3 ||
|
|
xmlhttprequest.readyState == 4) {
|
|
|
|
// Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data)
|
|
if (pollingMode == POLLING_ENABLED) {
|
|
if (xmlhttprequest.readyState == 3 && interval == null)
|
|
interval = setInterval(parseResponse, 30);
|
|
else if (xmlhttprequest.readyState == 4 && interval != null)
|
|
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 < 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 != null)
|
|
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 != null)
|
|
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.send(null);
|
|
|
|
return xmlhttprequest;
|
|
|
|
}
|
|
|
|
this.connect = function(data) {
|
|
|
|
// Start tunnel and connect synchronously
|
|
var connect_xmlhttprequest = new XMLHttpRequest();
|
|
connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, false);
|
|
connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
|
connect_xmlhttprequest.send(data);
|
|
|
|
// If failure, throw error
|
|
if (connect_xmlhttprequest.status != 200) {
|
|
var message = getHTTPTunnelErrorMessage(connect_xmlhttprequest);
|
|
throw new Error(message);
|
|
}
|
|
|
|
// Get UUID from response
|
|
tunnel_uuid = connect_xmlhttprequest.responseText;
|
|
|
|
// Start reading data
|
|
currentState = STATE_CONNECTED;
|
|
handleResponse(makeRequest());
|
|
|
|
};
|
|
|
|
this.disconnect = function() {
|
|
currentState = STATE_DISCONNECTED;
|
|
};
|
|
|
|
};
|
|
|
|
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 WebSocket protocol corresponding to the protocol used for the current
|
|
* location.
|
|
* @private
|
|
*/
|
|
var ws_protocol = {
|
|
"http:": "ws:",
|
|
"https:": "wss:"
|
|
};
|
|
|
|
var status_code = {
|
|
1000: "Connection closed normally.",
|
|
1001: "Connection shut down.",
|
|
1002: "Protocol error.",
|
|
1003: "Invalid data.",
|
|
1004: "[UNKNOWN, RESERVED]",
|
|
1005: "No status code present.",
|
|
1006: "Connection closed abnormally.",
|
|
1007: "Inconsistent data type.",
|
|
1008: "Policy violation.",
|
|
1009: "Message too large.",
|
|
1010: "Extension negotiation failed."
|
|
};
|
|
|
|
var STATE_IDLE = 0;
|
|
var STATE_CONNECTED = 1;
|
|
var STATE_DISCONNECTED = 2;
|
|
|
|
var currentState = STATE_IDLE;
|
|
|
|
// Transform current URL to WebSocket URL
|
|
|
|
// If not already a websocket URL
|
|
if ( tunnelURL.substring(0, 3) != "ws:"
|
|
&& 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.sendMessage = function(elements) {
|
|
|
|
// Do not attempt to send messages if not connected
|
|
if (currentState != STATE_CONNECTED)
|
|
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<arguments.length; i++)
|
|
message += "," + getElement(arguments[i]);
|
|
|
|
// Final terminator
|
|
message += ";";
|
|
|
|
socket.send(message);
|
|
|
|
};
|
|
|
|
this.connect = function(data) {
|
|
|
|
// Connect socket
|
|
socket = new WebSocket(tunnelURL + "?" + data, "guacamole");
|
|
|
|
socket.onopen = function(event) {
|
|
currentState = STATE_CONNECTED;
|
|
};
|
|
|
|
socket.onclose = function(event) {
|
|
|
|
// If connection closed abnormally, signal error.
|
|
if (event.code != 1000 && tunnel.onerror)
|
|
tunnel.onerror(status_code[event.code]);
|
|
|
|
};
|
|
|
|
socket.onerror = function(event) {
|
|
|
|
// Call error handler
|
|
if (tunnel.onerror) tunnel.onerror(event.data);
|
|
|
|
};
|
|
|
|
socket.onmessage = function(event) {
|
|
|
|
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
|
|
throw new 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();
|
|
|
|
// Call instruction handler.
|
|
if (tunnel.oninstruction != null)
|
|
tunnel.oninstruction(opcode, elements);
|
|
|
|
// Clear elements
|
|
elements.length = 0;
|
|
|
|
}
|
|
|
|
// Start searching for length at character after
|
|
// element terminator
|
|
startIndex = elementEnd + 1;
|
|
|
|
} while (startIndex < message.length);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
this.disconnect = function() {
|
|
currentState = STATE_DISCONNECTED;
|
|
socket.close();
|
|
};
|
|
|
|
};
|
|
|
|
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 {...} tunnel_chain The tunnels to use, in order of priority.
|
|
*/
|
|
Guacamole.ChainedTunnel = function(tunnel_chain) {
|
|
|
|
/**
|
|
* Reference to this chained tunnel.
|
|
* @private
|
|
*/
|
|
var chained_tunnel = this;
|
|
|
|
/**
|
|
* The currently wrapped tunnel, if any.
|
|
* @private
|
|
*/
|
|
var current_tunnel = null;
|
|
|
|
/**
|
|
* 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 = [];
|
|
|
|
// Load all tunnels into array
|
|
for (var i=0; i<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) {
|
|
|
|
// Clear handlers of current tunnel, if any
|
|
if (current_tunnel) {
|
|
current_tunnel.onerror = null;
|
|
current_tunnel.oninstruction = null;
|
|
}
|
|
|
|
// Set own functions to tunnel's functions
|
|
chained_tunnel.disconnect = tunnel.disconnect;
|
|
chained_tunnel.sendMessage = tunnel.sendMessage;
|
|
|
|
// Record current tunnel
|
|
current_tunnel = tunnel;
|
|
|
|
// Wrap own oninstruction within current tunnel
|
|
current_tunnel.oninstruction = function(opcode, elements) {
|
|
|
|
// Invoke handler
|
|
chained_tunnel.oninstruction(opcode, elements);
|
|
|
|
// Use handler permanently from now on
|
|
current_tunnel.oninstruction = chained_tunnel.oninstruction;
|
|
|
|
// Pass through errors (without trying other tunnels)
|
|
current_tunnel.onerror = chained_tunnel.onerror;
|
|
|
|
}
|
|
|
|
// Attach next tunnel on error
|
|
current_tunnel.onerror = function(message) {
|
|
|
|
// Get next tunnel
|
|
var next_tunnel = tunnels.shift();
|
|
|
|
// If there IS a next tunnel, try using it.
|
|
if (next_tunnel)
|
|
attach(next_tunnel);
|
|
|
|
// Otherwise, call error handler
|
|
else if (chained_tunnel.onerror)
|
|
chained_tunnel.onerror(message);
|
|
|
|
};
|
|
|
|
try {
|
|
|
|
// Attempt connection
|
|
current_tunnel.connect(connect_data);
|
|
|
|
}
|
|
catch (e) {
|
|
|
|
// Call error handler of current tunnel on error
|
|
current_tunnel.onerror(e.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.connect = function(data) {
|
|
|
|
// Remember connect data
|
|
connect_data = data;
|
|
|
|
// Get first tunnel
|
|
var next_tunnel = 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("No tunnels to try.");
|
|
|
|
};
|
|
|
|
};
|
|
|
|
Guacamole.ChainedTunnel.prototype = new Guacamole.Tunnel();
|