clockwerk-guacamole – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 1 | |
2 | /* ***** BEGIN LICENSE BLOCK ***** |
||
3 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
||
4 | * |
||
5 | * The contents of this file are subject to the Mozilla Public License Version |
||
6 | * 1.1 (the "License"); you may not use this file except in compliance with |
||
7 | * the License. You may obtain a copy of the License at |
||
8 | * http://www.mozilla.org/MPL/ |
||
9 | * |
||
10 | * Software distributed under the License is distributed on an "AS IS" basis, |
||
11 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
||
12 | * for the specific language governing rights and limitations under the |
||
13 | * License. |
||
14 | * |
||
15 | * The Original Code is guacamole-common-js. |
||
16 | * |
||
17 | * The Initial Developer of the Original Code is |
||
18 | * Michael Jumper. |
||
19 | * Portions created by the Initial Developer are Copyright (C) 2010 |
||
20 | * the Initial Developer. All Rights Reserved. |
||
21 | * |
||
22 | * Contributor(s): |
||
23 | * |
||
24 | * Alternatively, the contents of this file may be used under the terms of |
||
25 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
||
26 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
||
27 | * in which case the provisions of the GPL or the LGPL are applicable instead |
||
28 | * of those above. If you wish to allow use of your version of this file only |
||
29 | * under the terms of either the GPL or the LGPL, and not to allow others to |
||
30 | * use your version of this file under the terms of the MPL, indicate your |
||
31 | * decision by deleting the provisions above and replace them with the notice |
||
32 | * and other provisions required by the GPL or the LGPL. If you do not delete |
||
33 | * the provisions above, a recipient may use your version of this file under |
||
34 | * the terms of any one of the MPL, the GPL or the LGPL. |
||
35 | * |
||
36 | * ***** END LICENSE BLOCK ***** */ |
||
37 | |||
38 | // Guacamole namespace |
||
39 | var Guacamole = Guacamole || {}; |
||
40 | |||
41 | /** |
||
42 | * Core object providing abstract communication for Guacamole. This object |
||
43 | * is a null implementation whose functions do nothing. Guacamole applications |
||
44 | * should use {@link Guacamole.HTTPTunnel} instead, or implement their own tunnel based |
||
45 | * on this one. |
||
46 | * |
||
47 | * @constructor |
||
48 | * @see Guacamole.HTTPTunnel |
||
49 | */ |
||
50 | Guacamole.Tunnel = function() { |
||
51 | |||
52 | /** |
||
53 | * Connect to the tunnel with the given optional data. This data is |
||
54 | * typically used for authentication. The format of data accepted is |
||
55 | * up to the tunnel implementation. |
||
56 | * |
||
57 | * @param {String} data The data to send to the tunnel when connecting. |
||
58 | */ |
||
59 | this.connect = function(data) {}; |
||
60 | |||
61 | /** |
||
62 | * Disconnect from the tunnel. |
||
63 | */ |
||
64 | this.disconnect = function() {}; |
||
65 | |||
66 | /** |
||
67 | * Send the given message through the tunnel to the service on the other |
||
68 | * side. All messages are guaranteed to be received in the order sent. |
||
69 | * |
||
70 | * @param {...} elements The elements of the message to send to the |
||
71 | * service on the other side of the tunnel. |
||
72 | */ |
||
73 | this.sendMessage = function(elements) {}; |
||
74 | |||
75 | /** |
||
76 | * Fired whenever an error is encountered by the tunnel. |
||
77 | * |
||
78 | * @event |
||
79 | * @param {String} message A human-readable description of the error that |
||
80 | * occurred. |
||
81 | */ |
||
82 | this.onerror = null; |
||
83 | |||
84 | /** |
||
85 | * Fired once for every complete Guacamole instruction received, in order. |
||
86 | * |
||
87 | * @event |
||
88 | * @param {String} opcode The Guacamole instruction opcode. |
||
89 | * @param {Array} parameters The parameters provided for the instruction, |
||
90 | * if any. |
||
91 | */ |
||
92 | this.oninstruction = null; |
||
93 | |||
94 | }; |
||
95 | |||
96 | /** |
||
97 | * Guacamole Tunnel implemented over HTTP via XMLHttpRequest. |
||
98 | * |
||
99 | * @constructor |
||
100 | * @augments Guacamole.Tunnel |
||
101 | * @param {String} tunnelURL The URL of the HTTP tunneling service. |
||
102 | */ |
||
103 | Guacamole.HTTPTunnel = function(tunnelURL) { |
||
104 | |||
105 | /** |
||
106 | * Reference to this HTTP tunnel. |
||
107 | */ |
||
108 | var tunnel = this; |
||
109 | |||
110 | var tunnel_uuid; |
||
111 | |||
112 | var TUNNEL_CONNECT = tunnelURL + "?connect"; |
||
113 | var TUNNEL_READ = tunnelURL + "?read:"; |
||
114 | var TUNNEL_WRITE = tunnelURL + "?write:"; |
||
115 | |||
116 | var STATE_IDLE = 0; |
||
117 | var STATE_CONNECTED = 1; |
||
118 | var STATE_DISCONNECTED = 2; |
||
119 | |||
120 | var currentState = STATE_IDLE; |
||
121 | |||
122 | var POLLING_ENABLED = 1; |
||
123 | var POLLING_DISABLED = 0; |
||
124 | |||
125 | // Default to polling - will be turned off automatically if not needed |
||
126 | var pollingMode = POLLING_ENABLED; |
||
127 | |||
128 | var sendingMessages = false; |
||
129 | var outputMessageBuffer = ""; |
||
130 | |||
131 | this.sendMessage = function() { |
||
132 | |||
133 | // Do not attempt to send messages if not connected |
||
134 | if (currentState != STATE_CONNECTED) |
||
135 | return; |
||
136 | |||
137 | // Do not attempt to send empty messages |
||
138 | if (arguments.length == 0) |
||
139 | return; |
||
140 | |||
141 | /** |
||
142 | * Converts the given value to a length/string pair for use as an |
||
143 | * element in a Guacamole instruction. |
||
144 | * |
||
145 | * @param value The value to convert. |
||
146 | * @return {String} The converted value. |
||
147 | */ |
||
148 | function getElement(value) { |
||
149 | var string = new String(value); |
||
150 | return string.length + "." + string; |
||
151 | } |
||
152 | |||
153 | // Initialized message with first element |
||
154 | var message = getElement(arguments[0]); |
||
155 | |||
156 | // Append remaining elements |
||
157 | for (var i=1; i<arguments.length; i++) |
||
158 | message += "," + getElement(arguments[i]); |
||
159 | |||
160 | // Final terminator |
||
161 | message += ";"; |
||
162 | |||
163 | // Add message to buffer |
||
164 | outputMessageBuffer += message; |
||
165 | |||
166 | // Send if not currently sending |
||
167 | if (!sendingMessages) |
||
168 | sendPendingMessages(); |
||
169 | |||
170 | }; |
||
171 | |||
172 | function sendPendingMessages() { |
||
173 | |||
174 | if (outputMessageBuffer.length > 0) { |
||
175 | |||
176 | sendingMessages = true; |
||
177 | |||
178 | var message_xmlhttprequest = new XMLHttpRequest(); |
||
179 | message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid); |
||
180 | message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); |
||
181 | |||
182 | // Once response received, send next queued event. |
||
183 | message_xmlhttprequest.onreadystatechange = function() { |
||
184 | if (message_xmlhttprequest.readyState == 4) { |
||
185 | |||
186 | // If an error occurs during send, handle it |
||
187 | if (message_xmlhttprequest.status != 200) |
||
188 | handleHTTPTunnelError(message_xmlhttprequest); |
||
189 | |||
190 | // Otherwise, continue the send loop |
||
191 | else |
||
192 | sendPendingMessages(); |
||
193 | |||
194 | } |
||
195 | } |
||
196 | |||
197 | message_xmlhttprequest.send(outputMessageBuffer); |
||
198 | outputMessageBuffer = ""; // Clear buffer |
||
199 | |||
200 | } |
||
201 | else |
||
202 | sendingMessages = false; |
||
203 | |||
204 | } |
||
205 | |||
206 | function getHTTPTunnelErrorMessage(xmlhttprequest) { |
||
207 | |||
208 | var status = xmlhttprequest.status; |
||
209 | |||
210 | // Special cases |
||
211 | if (status == 0) return "Disconnected"; |
||
212 | if (status == 200) return "Success"; |
||
213 | if (status == 403) return "Unauthorized"; |
||
214 | if (status == 404) return "Connection closed"; /* While it may be more |
||
215 | * accurate to say the |
||
216 | * connection does not |
||
217 | * exist, it is confusing |
||
218 | * to the user. |
||
219 | * |
||
220 | * In general, this error |
||
221 | * will only happen when |
||
222 | * the tunnel does not |
||
223 | * exist, which happens |
||
224 | * after the connection |
||
225 | * is closed and the |
||
226 | * tunnel is detached. |
||
227 | */ |
||
228 | // Internal server errors |
||
229 | if (status >= 500 && status <= 599) return "Server error"; |
||
230 | |||
231 | // Otherwise, unknown |
||
232 | return "Unknown error"; |
||
233 | |||
234 | } |
||
235 | |||
236 | function handleHTTPTunnelError(xmlhttprequest) { |
||
237 | |||
238 | // Get error message |
||
239 | var message = getHTTPTunnelErrorMessage(xmlhttprequest); |
||
240 | |||
241 | // Call error handler |
||
242 | if (tunnel.onerror) tunnel.onerror(message); |
||
243 | |||
244 | // Finish |
||
245 | tunnel.disconnect(); |
||
246 | |||
247 | } |
||
248 | |||
249 | |||
250 | function handleResponse(xmlhttprequest) { |
||
251 | |||
252 | var interval = null; |
||
253 | var nextRequest = null; |
||
254 | |||
255 | var dataUpdateEvents = 0; |
||
256 | |||
257 | // The location of the last element's terminator |
||
258 | var elementEnd = -1; |
||
259 | |||
260 | // Where to start the next length search or the next element |
||
261 | var startIndex = 0; |
||
262 | |||
263 | // Parsed elements |
||
264 | var elements = new Array(); |
||
265 | |||
266 | function parseResponse() { |
||
267 | |||
268 | // Do not handle responses if not connected |
||
269 | if (currentState != STATE_CONNECTED) { |
||
270 | |||
271 | // Clean up interval if polling |
||
272 | if (interval != null) |
||
273 | clearInterval(interval); |
||
274 | |||
275 | return; |
||
276 | } |
||
277 | |||
278 | // Do not parse response yet if not ready |
||
279 | if (xmlhttprequest.readyState < 2) return; |
||
280 | |||
281 | // Attempt to read status |
||
282 | var status; |
||
283 | try { status = xmlhttprequest.status; } |
||
284 | |||
285 | // If status could not be read, assume successful. |
||
286 | catch (e) { status = 200; } |
||
287 | |||
288 | // Start next request as soon as possible IF request was successful |
||
289 | if (nextRequest == null && status == 200) |
||
290 | nextRequest = makeRequest(); |
||
291 | |||
292 | // Parse stream when data is received and when complete. |
||
293 | if (xmlhttprequest.readyState == 3 || |
||
294 | xmlhttprequest.readyState == 4) { |
||
295 | |||
296 | // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data) |
||
297 | if (pollingMode == POLLING_ENABLED) { |
||
298 | if (xmlhttprequest.readyState == 3 && interval == null) |
||
299 | interval = setInterval(parseResponse, 30); |
||
300 | else if (xmlhttprequest.readyState == 4 && interval != null) |
||
301 | clearInterval(interval); |
||
302 | } |
||
303 | |||
304 | // If canceled, stop transfer |
||
305 | if (xmlhttprequest.status == 0) { |
||
306 | tunnel.disconnect(); |
||
307 | return; |
||
308 | } |
||
309 | |||
310 | // Halt on error during request |
||
311 | else if (xmlhttprequest.status != 200) { |
||
312 | handleHTTPTunnelError(xmlhttprequest); |
||
313 | return; |
||
314 | } |
||
315 | |||
316 | // Attempt to read in-progress data |
||
317 | var current; |
||
318 | try { current = xmlhttprequest.responseText; } |
||
319 | |||
320 | // Do not attempt to parse if data could not be read |
||
321 | catch (e) { return; } |
||
322 | |||
323 | // While search is within currently received data |
||
324 | while (elementEnd < current.length) { |
||
325 | |||
326 | // If we are waiting for element data |
||
327 | if (elementEnd >= startIndex) { |
||
328 | |||
329 | // We now have enough data for the element. Parse. |
||
330 | var element = current.substring(startIndex, elementEnd); |
||
331 | var terminator = current.substring(elementEnd, elementEnd+1); |
||
332 | |||
333 | // Add element to array |
||
334 | elements.push(element); |
||
335 | |||
336 | // If last element, handle instruction |
||
337 | if (terminator == ";") { |
||
338 | |||
339 | // Get opcode |
||
340 | var opcode = elements.shift(); |
||
341 | |||
342 | // Call instruction handler. |
||
343 | if (tunnel.oninstruction != null) |
||
344 | tunnel.oninstruction(opcode, elements); |
||
345 | |||
346 | // Clear elements |
||
347 | elements.length = 0; |
||
348 | |||
349 | } |
||
350 | |||
351 | // Start searching for length at character after |
||
352 | // element terminator |
||
353 | startIndex = elementEnd + 1; |
||
354 | |||
355 | } |
||
356 | |||
357 | // Search for end of length |
||
358 | var lengthEnd = current.indexOf(".", startIndex); |
||
359 | if (lengthEnd != -1) { |
||
360 | |||
361 | // Parse length |
||
362 | var length = parseInt(current.substring(elementEnd+1, lengthEnd)); |
||
363 | |||
364 | // If we're done parsing, handle the next response. |
||
365 | if (length == 0) { |
||
366 | |||
367 | // Clean up interval if polling |
||
368 | if (interval != null) |
||
369 | clearInterval(interval); |
||
370 | |||
371 | // Clean up object |
||
372 | xmlhttprequest.onreadystatechange = null; |
||
373 | xmlhttprequest.abort(); |
||
374 | |||
375 | // Start handling next request |
||
376 | if (nextRequest) |
||
377 | handleResponse(nextRequest); |
||
378 | |||
379 | // Done parsing |
||
380 | break; |
||
381 | |||
382 | } |
||
383 | |||
384 | // Calculate start of element |
||
385 | startIndex = lengthEnd + 1; |
||
386 | |||
387 | // Calculate location of element terminator |
||
388 | elementEnd = startIndex + length; |
||
389 | |||
390 | } |
||
391 | |||
392 | // If no period yet, continue search when more data |
||
393 | // is received |
||
394 | else { |
||
395 | startIndex = current.length; |
||
396 | break; |
||
397 | } |
||
398 | |||
399 | } // end parse loop |
||
400 | |||
401 | } |
||
402 | |||
403 | } |
||
404 | |||
405 | // If response polling enabled, attempt to detect if still |
||
406 | // necessary (via wrapping parseResponse()) |
||
407 | if (pollingMode == POLLING_ENABLED) { |
||
408 | xmlhttprequest.onreadystatechange = function() { |
||
409 | |||
410 | // If we receive two or more readyState==3 events, |
||
411 | // there is no need to poll. |
||
412 | if (xmlhttprequest.readyState == 3) { |
||
413 | dataUpdateEvents++; |
||
414 | if (dataUpdateEvents >= 2) { |
||
415 | pollingMode = POLLING_DISABLED; |
||
416 | xmlhttprequest.onreadystatechange = parseResponse; |
||
417 | } |
||
418 | } |
||
419 | |||
420 | parseResponse(); |
||
421 | } |
||
422 | } |
||
423 | |||
424 | // Otherwise, just parse |
||
425 | else |
||
426 | xmlhttprequest.onreadystatechange = parseResponse; |
||
427 | |||
428 | parseResponse(); |
||
429 | |||
430 | } |
||
431 | |||
432 | |||
433 | function makeRequest() { |
||
434 | |||
435 | // Download self |
||
436 | var xmlhttprequest = new XMLHttpRequest(); |
||
437 | xmlhttprequest.open("POST", TUNNEL_READ + tunnel_uuid); |
||
438 | xmlhttprequest.send(null); |
||
439 | |||
440 | return xmlhttprequest; |
||
441 | |||
442 | } |
||
443 | |||
444 | this.connect = function(data) { |
||
445 | |||
446 | // Start tunnel and connect synchronously |
||
447 | var connect_xmlhttprequest = new XMLHttpRequest(); |
||
448 | connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, false); |
||
449 | connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); |
||
450 | connect_xmlhttprequest.send(data); |
||
451 | |||
452 | // If failure, throw error |
||
453 | if (connect_xmlhttprequest.status != 200) { |
||
454 | var message = getHTTPTunnelErrorMessage(connect_xmlhttprequest); |
||
455 | throw new Error(message); |
||
456 | } |
||
457 | |||
458 | // Get UUID from response |
||
459 | tunnel_uuid = connect_xmlhttprequest.responseText; |
||
460 | |||
461 | // Start reading data |
||
462 | currentState = STATE_CONNECTED; |
||
463 | handleResponse(makeRequest()); |
||
464 | |||
465 | }; |
||
466 | |||
467 | this.disconnect = function() { |
||
468 | currentState = STATE_DISCONNECTED; |
||
469 | }; |
||
470 | |||
471 | }; |
||
472 | |||
473 | Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel(); |
||
474 | |||
475 | |||
476 | /** |
||
477 | * Guacamole Tunnel implemented over WebSocket via XMLHttpRequest. |
||
478 | * |
||
479 | * @constructor |
||
480 | * @augments Guacamole.Tunnel |
||
481 | * @param {String} tunnelURL The URL of the WebSocket tunneling service. |
||
482 | */ |
||
483 | Guacamole.WebSocketTunnel = function(tunnelURL) { |
||
484 | |||
485 | /** |
||
486 | * Reference to this WebSocket tunnel. |
||
487 | */ |
||
488 | var tunnel = this; |
||
489 | |||
490 | /** |
||
491 | * The WebSocket used by this tunnel. |
||
492 | */ |
||
493 | var socket = null; |
||
494 | |||
495 | /** |
||
496 | * The WebSocket protocol corresponding to the protocol used for the current |
||
497 | * location. |
||
498 | */ |
||
499 | var ws_protocol = { |
||
500 | "http:": "ws:", |
||
501 | "https:": "wss:" |
||
502 | }; |
||
503 | |||
504 | var status_code = { |
||
505 | 1000: "Connection closed normally.", |
||
506 | 1001: "Connection shut down.", |
||
507 | 1002: "Protocol error.", |
||
508 | 1003: "Invalid data.", |
||
509 | 1004: "[UNKNOWN, RESERVED]", |
||
510 | 1005: "No status code present.", |
||
511 | 1006: "Connection closed abnormally.", |
||
512 | 1007: "Inconsistent data type.", |
||
513 | 1008: "Policy violation.", |
||
514 | 1009: "Message too large.", |
||
515 | 1010: "Extension negotiation failed." |
||
516 | }; |
||
517 | |||
518 | var STATE_IDLE = 0; |
||
519 | var STATE_CONNECTED = 1; |
||
520 | var STATE_DISCONNECTED = 2; |
||
521 | |||
522 | var currentState = STATE_IDLE; |
||
523 | |||
524 | // Transform current URL to WebSocket URL |
||
525 | |||
526 | // If not already a websocket URL |
||
527 | if ( tunnelURL.substring(0, 3) != "ws:" |
||
528 | && tunnelURL.substring(0, 4) != "wss:") { |
||
529 | |||
530 | var protocol = ws_protocol[window.location.protocol]; |
||
531 | |||
532 | // If absolute URL, convert to absolute WS URL |
||
533 | if (tunnelURL.substring(0, 1) == "/") |
||
534 | tunnelURL = |
||
535 | protocol |
||
536 | + "//" + window.location.host |
||
537 | + tunnelURL; |
||
538 | |||
539 | // Otherwise, construct absolute from relative URL |
||
540 | else { |
||
541 | |||
542 | // Get path from pathname |
||
543 | var slash = window.location.pathname.lastIndexOf("/"); |
||
544 | var path = window.location.pathname.substring(0, slash + 1); |
||
545 | |||
546 | // Construct absolute URL |
||
547 | tunnelURL = |
||
548 | protocol |
||
549 | + "//" + window.location.host |
||
550 | + path |
||
551 | + tunnelURL; |
||
552 | |||
553 | } |
||
554 | |||
555 | } |
||
556 | |||
557 | this.sendMessage = function(elements) { |
||
558 | |||
559 | // Do not attempt to send messages if not connected |
||
560 | if (currentState != STATE_CONNECTED) |
||
561 | return; |
||
562 | |||
563 | // Do not attempt to send empty messages |
||
564 | if (arguments.length == 0) |
||
565 | return; |
||
566 | |||
567 | /** |
||
568 | * Converts the given value to a length/string pair for use as an |
||
569 | * element in a Guacamole instruction. |
||
570 | * |
||
571 | * @param value The value to convert. |
||
572 | * @return {String} The converted value. |
||
573 | */ |
||
574 | function getElement(value) { |
||
575 | var string = new String(value); |
||
576 | return string.length + "." + string; |
||
577 | } |
||
578 | |||
579 | // Initialized message with first element |
||
580 | var message = getElement(arguments[0]); |
||
581 | |||
582 | // Append remaining elements |
||
583 | for (var i=1; i<arguments.length; i++) |
||
584 | message += "," + getElement(arguments[i]); |
||
585 | |||
586 | // Final terminator |
||
587 | message += ";"; |
||
588 | |||
589 | socket.send(message); |
||
590 | |||
591 | }; |
||
592 | |||
593 | this.connect = function(data) { |
||
594 | |||
595 | // Connect socket |
||
596 | socket = new WebSocket(tunnelURL + "?" + data, "guacamole"); |
||
597 | |||
598 | socket.onopen = function(event) { |
||
599 | currentState = STATE_CONNECTED; |
||
600 | }; |
||
601 | |||
602 | socket.onclose = function(event) { |
||
603 | |||
604 | // If connection closed abnormally, signal error. |
||
605 | if (event.code != 1000 && tunnel.onerror) |
||
606 | tunnel.onerror(status_code[event.code]); |
||
607 | |||
608 | }; |
||
609 | |||
610 | socket.onerror = function(event) { |
||
611 | |||
612 | // Call error handler |
||
613 | if (tunnel.onerror) tunnel.onerror(event.data); |
||
614 | |||
615 | }; |
||
616 | |||
617 | socket.onmessage = function(event) { |
||
618 | |||
619 | var message = event.data; |
||
620 | var startIndex = 0; |
||
621 | var elementEnd; |
||
622 | |||
623 | var elements = []; |
||
624 | |||
625 | do { |
||
626 | |||
627 | // Search for end of length |
||
628 | var lengthEnd = message.indexOf(".", startIndex); |
||
629 | if (lengthEnd != -1) { |
||
630 | |||
631 | // Parse length |
||
632 | var length = parseInt(message.substring(elementEnd+1, lengthEnd)); |
||
633 | |||
634 | // Calculate start of element |
||
635 | startIndex = lengthEnd + 1; |
||
636 | |||
637 | // Calculate location of element terminator |
||
638 | elementEnd = startIndex + length; |
||
639 | |||
640 | } |
||
641 | |||
642 | // If no period, incomplete instruction. |
||
643 | else |
||
644 | throw new Error("Incomplete instruction."); |
||
645 | |||
646 | // We now have enough data for the element. Parse. |
||
647 | var element = message.substring(startIndex, elementEnd); |
||
648 | var terminator = message.substring(elementEnd, elementEnd+1); |
||
649 | |||
650 | // Add element to array |
||
651 | elements.push(element); |
||
652 | |||
653 | // If last element, handle instruction |
||
654 | if (terminator == ";") { |
||
655 | |||
656 | // Get opcode |
||
657 | var opcode = elements.shift(); |
||
658 | |||
659 | // Call instruction handler. |
||
660 | if (tunnel.oninstruction != null) |
||
661 | tunnel.oninstruction(opcode, elements); |
||
662 | |||
663 | // Clear elements |
||
664 | elements.length = 0; |
||
665 | |||
666 | } |
||
667 | |||
668 | // Start searching for length at character after |
||
669 | // element terminator |
||
670 | startIndex = elementEnd + 1; |
||
671 | |||
672 | } while (startIndex < message.length); |
||
673 | |||
674 | }; |
||
675 | |||
676 | }; |
||
677 | |||
678 | this.disconnect = function() { |
||
679 | currentState = STATE_DISCONNECTED; |
||
680 | socket.close(); |
||
681 | }; |
||
682 | |||
683 | }; |
||
684 | |||
685 | Guacamole.WebSocketTunnel.prototype = new Guacamole.Tunnel(); |
||
686 | |||
687 | |||
688 | /** |
||
689 | * Guacamole Tunnel which cycles between all specified tunnels until |
||
690 | * no tunnels are left. Another tunnel is used if an error occurs but |
||
691 | * no instructions have been received. If an instruction has been |
||
692 | * received, or no tunnels remain, the error is passed directly out |
||
693 | * through the onerror handler (if defined). |
||
694 | * |
||
695 | * @constructor |
||
696 | * @augments Guacamole.Tunnel |
||
697 | * @param {...} tunnel_chain The tunnels to use, in order of priority. |
||
698 | */ |
||
699 | Guacamole.ChainedTunnel = function(tunnel_chain) { |
||
700 | |||
701 | /** |
||
702 | * Reference to this chained tunnel. |
||
703 | */ |
||
704 | var chained_tunnel = this; |
||
705 | |||
706 | /** |
||
707 | * The currently wrapped tunnel, if any. |
||
708 | */ |
||
709 | var current_tunnel = null; |
||
710 | |||
711 | /** |
||
712 | * Data passed in via connect(), to be used for |
||
713 | * wrapped calls to other tunnels' connect() functions. |
||
714 | */ |
||
715 | var connect_data; |
||
716 | |||
717 | /** |
||
718 | * Array of all tunnels passed to this ChainedTunnel through the |
||
719 | * constructor arguments. |
||
720 | */ |
||
721 | var tunnels = []; |
||
722 | |||
723 | // Load all tunnels into array |
||
724 | for (var i=0; i<arguments.length; i++) |
||
725 | tunnels.push(arguments[i]); |
||
726 | |||
727 | /** |
||
728 | * Sets the current tunnel |
||
729 | */ |
||
730 | function attach(tunnel) { |
||
731 | |||
732 | // Clear handlers of current tunnel, if any |
||
733 | if (current_tunnel) { |
||
734 | current_tunnel.onerror = null; |
||
735 | current_tunnel.oninstruction = null; |
||
736 | } |
||
737 | |||
738 | // Set own functions to tunnel's functions |
||
739 | chained_tunnel.disconnect = tunnel.disconnect; |
||
740 | chained_tunnel.sendMessage = tunnel.sendMessage; |
||
741 | |||
742 | // Record current tunnel |
||
743 | current_tunnel = tunnel; |
||
744 | |||
745 | // Wrap own oninstruction within current tunnel |
||
746 | current_tunnel.oninstruction = function(opcode, elements) { |
||
747 | |||
748 | // Invoke handler |
||
749 | chained_tunnel.oninstruction(opcode, elements); |
||
750 | |||
751 | // Use handler permanently from now on |
||
752 | current_tunnel.oninstruction = chained_tunnel.oninstruction; |
||
753 | |||
754 | // Pass through errors (without trying other tunnels) |
||
755 | current_tunnel.onerror = chained_tunnel.onerror; |
||
756 | |||
757 | } |
||
758 | |||
759 | // Attach next tunnel on error |
||
760 | current_tunnel.onerror = function(message) { |
||
761 | |||
762 | // Get next tunnel |
||
763 | var next_tunnel = tunnels.shift(); |
||
764 | |||
765 | // If there IS a next tunnel, try using it. |
||
766 | if (next_tunnel) |
||
767 | attach(next_tunnel); |
||
768 | |||
769 | // Otherwise, call error handler |
||
770 | else if (chained_tunnel.onerror) |
||
771 | chained_tunnel.onerror(message); |
||
772 | |||
773 | }; |
||
774 | |||
775 | try { |
||
776 | |||
777 | // Attempt connection |
||
778 | current_tunnel.connect(connect_data); |
||
779 | |||
780 | } |
||
781 | catch (e) { |
||
782 | |||
783 | // Call error handler of current tunnel on error |
||
784 | current_tunnel.onerror(e.message); |
||
785 | |||
786 | } |
||
787 | |||
788 | |||
789 | } |
||
790 | |||
791 | this.connect = function(data) { |
||
792 | |||
793 | // Remember connect data |
||
794 | connect_data = data; |
||
795 | |||
796 | // Get first tunnel |
||
797 | var next_tunnel = tunnels.shift(); |
||
798 | |||
799 | // Attach first tunnel |
||
800 | if (next_tunnel) |
||
801 | attach(next_tunnel); |
||
802 | |||
803 | // If there IS no first tunnel, error |
||
804 | else if (chained_tunnel.onerror) |
||
805 | chained_tunnel.onerror("No tunnels to try."); |
||
806 | |||
807 | }; |
||
808 | |||
809 | }; |
||
810 | |||
811 | Guacamole.ChainedTunnel.prototype = new Guacamole.Tunnel(); |