Merge pull request #950 from samhed/disconnectapi
Abstract information from RFB to the UI
This commit is contained in:
commit
db46e36eb9
|
@ -164,7 +164,7 @@ Localizer.prototype = {
|
|||
|
||||
process(document.body, true);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export var l10n = new Localizer();
|
||||
export default l10n.get.bind(l10n);
|
183
app/ui.js
183
app/ui.js
|
@ -12,7 +12,7 @@
|
|||
/* global window, document.getElementById, Util, WebUtil, RFB, Display */
|
||||
|
||||
import * as Log from '../core/util/logging.js';
|
||||
import _, { l10n } from '../core/util/localization.js';
|
||||
import _, { l10n } from './localization.js';
|
||||
import { isTouchDevice } from '../core/util/browsers.js';
|
||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||
import KeyTable from "../core/input/keysym.js";
|
||||
|
@ -105,7 +105,7 @@ var UI = {
|
|||
|
||||
UI.updateViewClip();
|
||||
|
||||
UI.updateVisualState();
|
||||
UI.updateVisualState('init');
|
||||
|
||||
document.documentElement.classList.remove("noVNC_loading");
|
||||
|
||||
|
@ -388,54 +388,41 @@ var UI = {
|
|||
* VISUAL
|
||||
* ------v------*/
|
||||
|
||||
updateState: function(event) {
|
||||
var msg;
|
||||
// Disable/enable controls depending on connection state
|
||||
updateVisualState: function(state) {
|
||||
|
||||
document.documentElement.classList.remove("noVNC_connecting");
|
||||
document.documentElement.classList.remove("noVNC_connected");
|
||||
document.documentElement.classList.remove("noVNC_disconnecting");
|
||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
||||
|
||||
switch (event.detail.state) {
|
||||
let transition_elem = document.getElementById("noVNC_transition_text");
|
||||
switch (state) {
|
||||
case 'init':
|
||||
break;
|
||||
case 'connecting':
|
||||
document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
|
||||
transition_elem.textContent = _("Connecting...");
|
||||
document.documentElement.classList.add("noVNC_connecting");
|
||||
break;
|
||||
case 'connected':
|
||||
UI.connected = true;
|
||||
UI.inhibit_reconnect = false;
|
||||
UI.doneInitialResize = false;
|
||||
document.documentElement.classList.add("noVNC_connected");
|
||||
if (UI.getSetting('encrypt')) {
|
||||
msg = _("Connected (encrypted) to ") + UI.desktopName;
|
||||
} else {
|
||||
msg = _("Connected (unencrypted) to ") + UI.desktopName;
|
||||
}
|
||||
UI.showStatus(msg);
|
||||
document.getElementById('noVNC_canvas').focus();
|
||||
break;
|
||||
case 'disconnecting':
|
||||
UI.connected = false;
|
||||
document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
|
||||
transition_elem.textContent = _("Disconnecting...");
|
||||
document.documentElement.classList.add("noVNC_disconnecting");
|
||||
break;
|
||||
case 'disconnected':
|
||||
UI.showStatus(_("Disconnected"));
|
||||
break;
|
||||
case 'reconnecting':
|
||||
transition_elem.textContent = _("Reconnecting...");
|
||||
document.documentElement.classList.add("noVNC_reconnecting");
|
||||
break;
|
||||
default:
|
||||
msg = "Invalid UI state";
|
||||
Log.Error(msg);
|
||||
UI.showStatus(msg, 'error');
|
||||
break;
|
||||
Log.Error("Invalid visual state: " + state);
|
||||
UI.showStatus(_("Internal error"), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
UI.updateVisualState();
|
||||
},
|
||||
|
||||
// Disable/enable controls depending on connection state
|
||||
updateVisualState: function() {
|
||||
//Log.Debug(">> updateVisualState");
|
||||
|
||||
UI.enableDisableViewClip();
|
||||
|
||||
if (UI.connected) {
|
||||
|
@ -481,8 +468,6 @@ var UI = {
|
|||
// State change also closes the password dialog
|
||||
document.getElementById('noVNC_password_dlg')
|
||||
.classList.remove('noVNC_open');
|
||||
|
||||
//Log.Debug("<< updateVisualState");
|
||||
},
|
||||
|
||||
showStatus: function(text, status_type, time) {
|
||||
|
@ -494,21 +479,40 @@ var UI = {
|
|||
status_type = 'normal';
|
||||
}
|
||||
|
||||
statusElem.classList.remove("noVNC_status_normal");
|
||||
statusElem.classList.remove("noVNC_status_warn");
|
||||
statusElem.classList.remove("noVNC_status_error");
|
||||
// Don't overwrite more severe visible statuses and never
|
||||
// errors. Only shows the first error.
|
||||
let visible_status_type = 'none';
|
||||
if (statusElem.classList.contains("noVNC_open")) {
|
||||
if (statusElem.classList.contains("noVNC_status_error")) {
|
||||
visible_status_type = 'error';
|
||||
} else if (statusElem.classList.contains("noVNC_status_warn")) {
|
||||
visible_status_type = 'warn';
|
||||
} else {
|
||||
visible_status_type = 'normal';
|
||||
}
|
||||
}
|
||||
if (visible_status_type === 'error' ||
|
||||
(visible_status_type === 'warn' && status_type === 'normal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (status_type) {
|
||||
case 'error':
|
||||
statusElem.classList.remove("noVNC_status_warn");
|
||||
statusElem.classList.remove("noVNC_status_normal");
|
||||
statusElem.classList.add("noVNC_status_error");
|
||||
break;
|
||||
case 'warning':
|
||||
case 'warn':
|
||||
statusElem.classList.remove("noVNC_status_error");
|
||||
statusElem.classList.remove("noVNC_status_normal");
|
||||
statusElem.classList.add("noVNC_status_warn");
|
||||
break;
|
||||
case 'error':
|
||||
statusElem.classList.add("noVNC_status_error");
|
||||
break;
|
||||
case 'normal':
|
||||
case 'info':
|
||||
default:
|
||||
statusElem.classList.remove("noVNC_status_error");
|
||||
statusElem.classList.remove("noVNC_status_warn");
|
||||
statusElem.classList.add("noVNC_status_normal");
|
||||
break;
|
||||
}
|
||||
|
@ -532,10 +536,6 @@ var UI = {
|
|||
document.getElementById('noVNC_status').classList.remove("noVNC_open");
|
||||
},
|
||||
|
||||
notification: function (e) {
|
||||
UI.showStatus(e.detail.message, e.detail.level);
|
||||
},
|
||||
|
||||
activateControlbar: function(event) {
|
||||
clearTimeout(UI.idleControlbarTimeout);
|
||||
// We manipulate the anchor instead of the actual control
|
||||
|
@ -1012,16 +1012,19 @@ var UI = {
|
|||
password = undefined;
|
||||
}
|
||||
|
||||
UI.hideStatus();
|
||||
|
||||
if (!host) {
|
||||
var msg = _("Must set host");
|
||||
Log.Error(msg);
|
||||
UI.showStatus(msg, 'error');
|
||||
Log.Error("Can't connect when host is: " + host);
|
||||
UI.showStatus(_("Must set host"), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
UI.closeAllPanels();
|
||||
UI.closeConnectPanel();
|
||||
|
||||
UI.updateVisualState('connecting');
|
||||
|
||||
UI.updateViewOnly();
|
||||
|
||||
var url;
|
||||
|
@ -1038,10 +1041,10 @@ var UI = {
|
|||
{ shared: UI.getSetting('shared'),
|
||||
repeaterID: UI.getSetting('repeaterID'),
|
||||
credentials: { password: password } });
|
||||
UI.rfb.addEventListener("notification", UI.notification);
|
||||
UI.rfb.addEventListener("updatestate", UI.updateState);
|
||||
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
||||
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
||||
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
|
||||
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
|
||||
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
||||
UI.rfb.addEventListener("bell", UI.bell);
|
||||
|
@ -1053,9 +1056,13 @@ var UI = {
|
|||
UI.closeAllPanels();
|
||||
UI.rfb.disconnect();
|
||||
|
||||
UI.connected = false;
|
||||
|
||||
// Disable automatic reconnecting
|
||||
UI.inhibit_reconnect = true;
|
||||
|
||||
UI.updateVisualState('disconnecting');
|
||||
|
||||
// Don't display the connection settings until we're actually disconnected
|
||||
},
|
||||
|
||||
|
@ -1070,33 +1077,76 @@ var UI = {
|
|||
UI.connect(null, UI.reconnect_password);
|
||||
},
|
||||
|
||||
disconnectFinished: function (e) {
|
||||
if (typeof e.detail.reason !== 'undefined') {
|
||||
UI.showStatus(e.detail.reason, 'error');
|
||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
||||
document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
|
||||
document.documentElement.classList.add("noVNC_reconnecting");
|
||||
|
||||
var delay = parseInt(UI.getSetting('reconnect_delay'));
|
||||
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
|
||||
return;
|
||||
}
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
},
|
||||
|
||||
cancelReconnect: function() {
|
||||
if (UI.reconnect_callback !== null) {
|
||||
clearTimeout(UI.reconnect_callback);
|
||||
UI.reconnect_callback = null;
|
||||
}
|
||||
|
||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
||||
UI.updateVisualState('disconnected');
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
},
|
||||
|
||||
connectFinished: function (e) {
|
||||
UI.connected = true;
|
||||
UI.inhibit_reconnect = false;
|
||||
UI.doneInitialResize = false;
|
||||
|
||||
let msg;
|
||||
if (UI.getSetting('encrypt')) {
|
||||
msg = _("Connected (encrypted) to ") + UI.desktopName;
|
||||
} else {
|
||||
msg = _("Connected (unencrypted) to ") + UI.desktopName;
|
||||
}
|
||||
UI.showStatus(msg);
|
||||
UI.updateVisualState('connected');
|
||||
|
||||
// Do this last because it can only be used on rendered elements
|
||||
document.getElementById('noVNC_canvas').focus();
|
||||
},
|
||||
|
||||
disconnectFinished: function (e) {
|
||||
// This variable is ideally set when disconnection starts, but
|
||||
// when the disconnection isn't clean or if it is initiated by
|
||||
// the server, we need to do it here as well since
|
||||
// UI.disconnect() won't be used in those cases.
|
||||
UI.connected = false;
|
||||
|
||||
if (!e.detail.clean) {
|
||||
UI.updateVisualState('disconnected');
|
||||
UI.showStatus(_("Something went wrong, connection is closed"),
|
||||
'error');
|
||||
} else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
|
||||
UI.updateVisualState('reconnecting');
|
||||
|
||||
var delay = parseInt(UI.getSetting('reconnect_delay'));
|
||||
UI.reconnect_callback = setTimeout(UI.reconnect, delay);
|
||||
return;
|
||||
} else {
|
||||
UI.updateVisualState('disconnected');
|
||||
UI.showStatus(_("Disconnected"), 'normal');
|
||||
}
|
||||
|
||||
UI.openControlbar();
|
||||
UI.openConnectPanel();
|
||||
},
|
||||
|
||||
securityFailed: function (e) {
|
||||
let msg = "";
|
||||
// On security failures we might get a string with a reason
|
||||
// directly from the server. Note that we can't control if
|
||||
// this string is translated or not.
|
||||
if ('reason' in e.detail) {
|
||||
msg = _("New connection has been rejected with reason: ") +
|
||||
e.detail.reason;
|
||||
} else {
|
||||
msg = _("New connection has been rejected");
|
||||
}
|
||||
UI.showStatus(msg, 'error');
|
||||
},
|
||||
|
||||
/* ------^-------
|
||||
* /CONNECTION
|
||||
* ==============
|
||||
|
@ -1112,9 +1162,8 @@ var UI = {
|
|||
document.getElementById('noVNC_password_input').focus();
|
||||
}, 100);
|
||||
|
||||
var msg = _("Password is required");
|
||||
Log.Warn(msg);
|
||||
UI.showStatus(msg, "warning");
|
||||
Log.Warn("Server asked for a password");
|
||||
UI.showStatus(_("Password is required"), "warning");
|
||||
},
|
||||
|
||||
setPassword: function(e) {
|
||||
|
|
|
@ -26,7 +26,7 @@ export var encodings = {
|
|||
pseudoEncodingContinuousUpdates: -313,
|
||||
pseudoEncodingCompressLevel9: -247,
|
||||
pseudoEncodingCompressLevel0: -256,
|
||||
}
|
||||
};
|
||||
|
||||
export function encodingName(num) {
|
||||
switch (num) {
|
||||
|
|
271
core/rfb.js
271
core/rfb.js
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2016 Samuel Mannehed for Cendio AB
|
||||
* Copyright (C) 2017 Samuel Mannehed for Cendio AB
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
|
||||
import * as Log from './util/logging.js';
|
||||
import _ from './util/localization.js';
|
||||
import { decodeUTF8 } from './util/strings.js';
|
||||
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
|
||||
import EventTargetMixin from './util/eventtarget.js';
|
||||
|
@ -45,7 +44,7 @@ export default function RFB(target, url, options) {
|
|||
this._url = url;
|
||||
|
||||
// Connection details
|
||||
options = options || {}
|
||||
options = options || {};
|
||||
this._rfb_credentials = options.credentials || {};
|
||||
this._shared = 'shared' in options ? !!options.shared : true;
|
||||
this._repeaterID = options.repeaterID || '';
|
||||
|
@ -54,7 +53,7 @@ export default function RFB(target, url, options) {
|
|||
this._rfb_connection_state = '';
|
||||
this._rfb_init_state = '';
|
||||
this._rfb_auth_scheme = '';
|
||||
this._rfb_disconnect_reason = "";
|
||||
this._rfb_clean_disconnect = true;
|
||||
|
||||
// Server capabilities
|
||||
this._rfb_version = 0;
|
||||
|
@ -190,38 +189,40 @@ export default function RFB(target, url, options) {
|
|||
this._rfb_init_state = 'ProtocolVersion';
|
||||
Log.Debug("Starting VNC handshake");
|
||||
} else {
|
||||
this._fail("Unexpected server connection");
|
||||
this._fail("Unexpected server connection while " +
|
||||
this._rfb_connection_state);
|
||||
}
|
||||
}.bind(this));
|
||||
this._sock.on('close', function (e) {
|
||||
Log.Warn("WebSocket on-close event");
|
||||
var msg = "";
|
||||
if (e.code) {
|
||||
msg = " (code: " + e.code;
|
||||
msg = "(code: " + e.code;
|
||||
if (e.reason) {
|
||||
msg += ", reason: " + e.reason;
|
||||
}
|
||||
msg += ")";
|
||||
}
|
||||
switch (this._rfb_connection_state) {
|
||||
case 'disconnecting':
|
||||
this._updateConnectionState('disconnected');
|
||||
break;
|
||||
case 'connecting':
|
||||
this._fail('Failed to connect to server', msg);
|
||||
this._fail("Connection closed " + msg);
|
||||
break;
|
||||
case 'connected':
|
||||
// Handle disconnects that were initiated server-side
|
||||
this._updateConnectionState('disconnecting');
|
||||
this._updateConnectionState('disconnected');
|
||||
break;
|
||||
case 'disconnecting':
|
||||
// Normal disconnection path
|
||||
this._updateConnectionState('disconnected');
|
||||
break;
|
||||
case 'disconnected':
|
||||
this._fail("Unexpected server disconnect",
|
||||
"Already disconnected: " + msg);
|
||||
this._fail("Unexpected server disconnect " +
|
||||
"when already disconnected " + msg);
|
||||
break;
|
||||
default:
|
||||
this._fail("Unexpected server disconnect",
|
||||
"Not in any state yet: " + msg);
|
||||
this._fail("Unexpected server disconnect before connecting " +
|
||||
msg);
|
||||
break;
|
||||
}
|
||||
this._sock.off('close');
|
||||
|
@ -384,9 +385,9 @@ RFB.prototype = {
|
|||
this._sock.open(this._url, ['binary']);
|
||||
} catch (e) {
|
||||
if (e.name === 'SyntaxError') {
|
||||
this._fail("Invalid host or port value given", e);
|
||||
this._fail("Invalid host or port (" + e + ")");
|
||||
} else {
|
||||
this._fail("Error while connecting", e);
|
||||
this._fail("Error when opening socket (" + e + ")");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,8 +513,6 @@ RFB.prototype = {
|
|||
// State change actions
|
||||
|
||||
this._rfb_connection_state = state;
|
||||
var event = new CustomEvent("updatestate", { detail: { state: state } });
|
||||
this.dispatchEvent(event);
|
||||
|
||||
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
|
||||
Log.Debug(smsg);
|
||||
|
@ -528,59 +527,54 @@ RFB.prototype = {
|
|||
}
|
||||
|
||||
switch (state) {
|
||||
case 'disconnected':
|
||||
// Fire disconnected event after updatestate event since
|
||||
// we don't know if the UI only displays the latest message
|
||||
if (this._rfb_disconnect_reason !== "") {
|
||||
event = new CustomEvent("disconnect",
|
||||
{ detail: { reason: this._rfb_disconnect_reason } });
|
||||
} else {
|
||||
// No reason means clean disconnect
|
||||
event = new CustomEvent("disconnect", { detail: {} });
|
||||
}
|
||||
this.dispatchEvent(event);
|
||||
break;
|
||||
|
||||
case 'connecting':
|
||||
this._connect();
|
||||
break;
|
||||
|
||||
case 'connected':
|
||||
var event = new CustomEvent("connect", { detail: {} });
|
||||
this.dispatchEvent(event);
|
||||
break;
|
||||
|
||||
case 'disconnecting':
|
||||
this._disconnect();
|
||||
|
||||
this._disconnTimer = setTimeout(function () {
|
||||
this._rfb_disconnect_reason = _("Disconnect timeout");
|
||||
Log.Error("Disconnection timed out.");
|
||||
this._updateConnectionState('disconnected');
|
||||
}.bind(this), DISCONNECT_TIMEOUT * 1000);
|
||||
break;
|
||||
|
||||
case 'disconnected':
|
||||
event = new CustomEvent(
|
||||
"disconnect", { detail:
|
||||
{ clean: this._rfb_clean_disconnect } });
|
||||
this.dispatchEvent(event);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/* Print errors and disconnect
|
||||
*
|
||||
* The optional parameter 'details' is used for information that
|
||||
* The parameter 'details' is used for information that
|
||||
* should be logged but not sent to the user interface.
|
||||
*/
|
||||
_fail: function (msg, details) {
|
||||
var fullmsg = msg;
|
||||
if (typeof details !== 'undefined') {
|
||||
fullmsg = msg + " (" + details + ")";
|
||||
}
|
||||
_fail: function (details) {
|
||||
switch (this._rfb_connection_state) {
|
||||
case 'disconnecting':
|
||||
Log.Error("Failed when disconnecting: " + fullmsg);
|
||||
Log.Error("Failed when disconnecting: " + details);
|
||||
break;
|
||||
case 'connected':
|
||||
Log.Error("Failed while connected: " + fullmsg);
|
||||
Log.Error("Failed while connected: " + details);
|
||||
break;
|
||||
case 'connecting':
|
||||
Log.Error("Failed when connecting: " + fullmsg);
|
||||
Log.Error("Failed when connecting: " + details);
|
||||
break;
|
||||
default:
|
||||
Log.Error("RFB failure: " + fullmsg);
|
||||
Log.Error("RFB failure: " + details);
|
||||
break;
|
||||
}
|
||||
this._rfb_disconnect_reason = msg; //This is sent to the UI
|
||||
this._rfb_clean_disconnect = false; //This is sent to the UI
|
||||
|
||||
// Transition to disconnected without waiting for socket to close
|
||||
this._updateConnectionState('disconnecting');
|
||||
|
@ -589,30 +583,6 @@ RFB.prototype = {
|
|||
return false;
|
||||
},
|
||||
|
||||
/*
|
||||
* Send a notification to the UI. Valid levels are:
|
||||
* 'normal'|'warn'|'error'
|
||||
*
|
||||
* NOTE: If this function is called multiple times, remember that the
|
||||
* interface could be only showing the latest notification.
|
||||
*/
|
||||
_notification: function(msg, level) {
|
||||
switch (level) {
|
||||
case 'normal':
|
||||
case 'warn':
|
||||
case 'error':
|
||||
Log.Debug("Notification[" + level + "]:" + msg);
|
||||
break;
|
||||
default:
|
||||
Log.Error("Invalid notification level: " + level);
|
||||
return;
|
||||
}
|
||||
|
||||
var event = new CustomEvent("notification",
|
||||
{ detail: { message: msg, level: level } });
|
||||
this.dispatchEvent(event);
|
||||
},
|
||||
|
||||
_setCapability: function (cap, val) {
|
||||
this._capabilities[cap] = val;
|
||||
var event = new CustomEvent("capabilities",
|
||||
|
@ -716,8 +686,7 @@ RFB.prototype = {
|
|||
|
||||
_negotiate_protocol_version: function () {
|
||||
if (this._sock.rQlen() < 12) {
|
||||
return this._fail("Error while negotiating with server",
|
||||
"Incomplete protocol version");
|
||||
return this._fail("Received incomplete protocol version.");
|
||||
}
|
||||
|
||||
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
|
||||
|
@ -742,8 +711,7 @@ RFB.prototype = {
|
|||
this._rfb_version = 3.8;
|
||||
break;
|
||||
default:
|
||||
return this._fail("Unsupported server",
|
||||
"Invalid server version: " + sversion);
|
||||
return this._fail("Invalid server version " + sversion);
|
||||
}
|
||||
|
||||
if (is_repeater) {
|
||||
|
@ -785,10 +753,7 @@ RFB.prototype = {
|
|||
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
|
||||
|
||||
if (num_types === 0) {
|
||||
var strlen = this._sock.rQshift32();
|
||||
var reason = this._sock.rQshiftStr(strlen);
|
||||
return this._fail("Error while negotiating with server",
|
||||
"Security failure: " + reason);
|
||||
return this._handle_security_failure("no security types");
|
||||
}
|
||||
|
||||
var types = this._sock.rQshiftBytes(num_types);
|
||||
|
@ -805,8 +770,7 @@ RFB.prototype = {
|
|||
} else if (includes(2, types)) {
|
||||
this._rfb_auth_scheme = 2; // VNC Auth
|
||||
} else {
|
||||
return this._fail("Unsupported server",
|
||||
"Unsupported security types: " + types);
|
||||
return this._fail("Unsupported security types (types: " + types + ")");
|
||||
}
|
||||
|
||||
this._sock.send([this._rfb_auth_scheme]);
|
||||
|
@ -822,6 +786,59 @@ RFB.prototype = {
|
|||
return this._init_msg(); // jump to authentication
|
||||
},
|
||||
|
||||
/*
|
||||
* Get the security failure reason if sent from the server and
|
||||
* send the 'securityfailure' event.
|
||||
*
|
||||
* - The optional parameter context can be used to add some extra
|
||||
* context to the log output.
|
||||
*
|
||||
* - The optional parameter security_result_status can be used to
|
||||
* add a custom status code to the event.
|
||||
*/
|
||||
_handle_security_failure: function (context, security_result_status) {
|
||||
|
||||
if (typeof context === 'undefined') {
|
||||
context = "";
|
||||
} else {
|
||||
context = " on " + context;
|
||||
}
|
||||
|
||||
if (typeof security_result_status === 'undefined') {
|
||||
security_result_status = 1; // fail
|
||||
}
|
||||
|
||||
if (this._sock.rQwait("reason length", 4)) {
|
||||
return false;
|
||||
}
|
||||
let strlen = this._sock.rQshift32();
|
||||
let reason = "";
|
||||
|
||||
if (strlen > 0) {
|
||||
if (this._sock.rQwait("reason", strlen, 8)) { return false; }
|
||||
reason = this._sock.rQshiftStr(strlen);
|
||||
}
|
||||
|
||||
if (reason !== "") {
|
||||
|
||||
let event = new CustomEvent(
|
||||
"securityfailure",
|
||||
{ detail: { status: security_result_status, reason: reason } });
|
||||
this.dispatchEvent(event);
|
||||
|
||||
return this._fail("Security negotiation failed" + context +
|
||||
" (reason: " + reason + ")");
|
||||
} else {
|
||||
|
||||
let event = new CustomEvent(
|
||||
"securityfailure",
|
||||
{ detail: { status: security_result_status } });
|
||||
this.dispatchEvent(event);
|
||||
|
||||
return this._fail("Security negotiation failed" + context);
|
||||
}
|
||||
},
|
||||
|
||||
// authentication
|
||||
_negotiate_xvp_auth: function () {
|
||||
if (!this._rfb_credentials.username ||
|
||||
|
@ -877,15 +894,13 @@ RFB.prototype = {
|
|||
if (serverSupportedTunnelTypes[0]) {
|
||||
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
|
||||
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
|
||||
return this._fail("Unsupported server",
|
||||
"Client's tunnel type had the incorrect " +
|
||||
return this._fail("Client's tunnel type had the incorrect " +
|
||||
"vendor or signature");
|
||||
}
|
||||
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
||||
return false; // wait until we receive the sub auth count to continue
|
||||
} else {
|
||||
return this._fail("Unsupported server",
|
||||
"Server wanted tunnels, but doesn't support " +
|
||||
return this._fail("Server wanted tunnels, but doesn't support " +
|
||||
"the notunnel type");
|
||||
}
|
||||
},
|
||||
|
@ -939,24 +954,19 @@ RFB.prototype = {
|
|||
this._rfb_auth_scheme = 2;
|
||||
return this._init_msg();
|
||||
default:
|
||||
return this._fail("Unsupported server",
|
||||
"Unsupported tiny auth scheme: " +
|
||||
authType);
|
||||
return this._fail("Unsupported tiny auth scheme " +
|
||||
"(scheme: " + authType + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._fail("Unsupported server",
|
||||
"No supported sub-auth types!");
|
||||
return this._fail("No supported sub-auth types!");
|
||||
},
|
||||
|
||||
_negotiate_authentication: function () {
|
||||
switch (this._rfb_auth_scheme) {
|
||||
case 0: // connection failed
|
||||
if (this._sock.rQwait("auth reason", 4)) { return false; }
|
||||
var strlen = this._sock.rQshift32();
|
||||
var reason = this._sock.rQshiftStr(strlen);
|
||||
return this._fail("Authentication failure", reason);
|
||||
return this._handle_security_failure("authentication scheme");
|
||||
|
||||
case 1: // no auth
|
||||
if (this._rfb_version >= 3.8) {
|
||||
|
@ -976,33 +986,30 @@ RFB.prototype = {
|
|||
return this._negotiate_tight_auth();
|
||||
|
||||
default:
|
||||
return this._fail("Unsupported server",
|
||||
"Unsupported auth scheme: " +
|
||||
this._rfb_auth_scheme);
|
||||
return this._fail("Unsupported auth scheme (scheme: " +
|
||||
this._rfb_auth_scheme + ")");
|
||||
}
|
||||
},
|
||||
|
||||
_handle_security_result: function () {
|
||||
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
||||
switch (this._sock.rQshift32()) {
|
||||
case 0: // OK
|
||||
this._rfb_init_state = 'ClientInitialisation';
|
||||
Log.Debug('Authentication OK');
|
||||
return this._init_msg();
|
||||
case 1: // failed
|
||||
if (this._rfb_version >= 3.8) {
|
||||
var length = this._sock.rQshift32();
|
||||
if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
|
||||
var reason = this._sock.rQshiftStr(length);
|
||||
return this._fail("Authentication failure", reason);
|
||||
} else {
|
||||
return this._fail("Authentication failure");
|
||||
}
|
||||
case 2:
|
||||
return this._fail("Too many authentication attempts");
|
||||
default:
|
||||
return this._fail("Unsupported server",
|
||||
"Unknown SecurityResult");
|
||||
|
||||
let status = this._sock.rQshift32();
|
||||
|
||||
if (status === 0) { // OK
|
||||
this._rfb_init_state = 'ClientInitialisation';
|
||||
Log.Debug('Authentication OK');
|
||||
return this._init_msg();
|
||||
} else {
|
||||
if (this._rfb_version >= 3.8) {
|
||||
return this._handle_security_failure("security result", status);
|
||||
} else {
|
||||
let event = new CustomEvent("securityfailure",
|
||||
{ detail: { status: status } });
|
||||
this.dispatchEvent(event);
|
||||
|
||||
return this._fail("Security handshake failed");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1181,15 +1188,15 @@ RFB.prototype = {
|
|||
return this._negotiate_server_init();
|
||||
|
||||
default:
|
||||
return this._fail("Internal error", "Unknown init state: " +
|
||||
this._rfb_init_state);
|
||||
return this._fail("Unknown init state (state: " +
|
||||
this._rfb_init_state + ")");
|
||||
}
|
||||
},
|
||||
|
||||
_handle_set_colour_map_msg: function () {
|
||||
Log.Debug("SetColorMapEntries");
|
||||
|
||||
return this._fail("Protocol error", "Unexpected SetColorMapEntries message");
|
||||
return this._fail("Unexpected SetColorMapEntries message");
|
||||
},
|
||||
|
||||
_handle_server_cut_text: function () {
|
||||
|
@ -1238,8 +1245,7 @@ RFB.prototype = {
|
|||
*/
|
||||
|
||||
if (!(flags & (1<<31))) {
|
||||
return this._fail("Internal error",
|
||||
"Unexpected fence response");
|
||||
return this._fail("Unexpected fence response");
|
||||
}
|
||||
|
||||
// Filter out unsupported flags
|
||||
|
@ -1262,8 +1268,7 @@ RFB.prototype = {
|
|||
|
||||
switch (xvp_msg) {
|
||||
case 0: // XVP_FAIL
|
||||
Log.Error("Operation Failed");
|
||||
this._notification("XVP Operation Failed", 'error');
|
||||
Log.Error("XVP Operation Failed");
|
||||
break;
|
||||
case 1: // XVP_INIT
|
||||
this._rfb_xvp_ver = xvp_ver;
|
||||
|
@ -1271,8 +1276,7 @@ RFB.prototype = {
|
|||
this._setCapability("power", true);
|
||||
break;
|
||||
default:
|
||||
this._fail("Unexpected server message",
|
||||
"Illegal server XVP message " + xvp_msg);
|
||||
this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1330,7 +1334,7 @@ RFB.prototype = {
|
|||
return this._handle_xvp_msg();
|
||||
|
||||
default:
|
||||
this._fail("Unexpected server message", "Type:" + msg_type);
|
||||
this._fail("Unexpected server message (type " + msg_type + ")");
|
||||
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
||||
return true;
|
||||
}
|
||||
|
@ -1385,9 +1389,8 @@ RFB.prototype = {
|
|||
(hdr[10] << 8) + hdr[11], 10);
|
||||
|
||||
if (!this._encHandlers[this._FBU.encoding]) {
|
||||
this._fail("Unexpected server message",
|
||||
"Unsupported encoding " +
|
||||
this._FBU.encoding);
|
||||
this._fail("Unsupported encoding (encoding: " +
|
||||
this._FBU.encoding + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1881,8 +1884,8 @@ RFB.encodingHandlers = {
|
|||
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
|
||||
var subencoding = rQ[rQi]; // Peek
|
||||
if (subencoding > 30) { // Raw
|
||||
this._fail("Unexpected server message",
|
||||
"Illegal hextile subencoding: " + subencoding);
|
||||
this._fail("Illegal hextile subencoding (subencoding: " +
|
||||
subencoding + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2211,9 +2214,8 @@ RFB.encodingHandlers = {
|
|||
else if (ctl === 0x0A) cmode = "png";
|
||||
else if (ctl & 0x04) cmode = "filter";
|
||||
else if (ctl < 0x04) cmode = "copy";
|
||||
else return this._fail("Unexpected server message",
|
||||
"Illegal tight compression received, " +
|
||||
"ctl: " + ctl);
|
||||
else return this._fail("Illegal tight compression received (ctl: " +
|
||||
ctl + ")");
|
||||
|
||||
switch (cmode) {
|
||||
// fill use depth because TPIXELs drop the padding byte
|
||||
|
@ -2273,9 +2275,8 @@ RFB.encodingHandlers = {
|
|||
} else {
|
||||
// Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
|
||||
// Filter 2, Gradient is valid but not use if jpeg is enabled
|
||||
this._fail("Unexpected server message",
|
||||
"Unsupported tight subencoding received, " +
|
||||
"filter: " + filterId);
|
||||
this._fail("Unsupported tight subencoding received " +
|
||||
"(filter: " + filterId + ")");
|
||||
}
|
||||
break;
|
||||
case "copy":
|
||||
|
@ -2350,8 +2351,8 @@ RFB.encodingHandlers = {
|
|||
msg = "Unknown reason";
|
||||
break;
|
||||
}
|
||||
this._notification("Server did not accept the resize request: "
|
||||
+ msg, 'normal');
|
||||
Log.Warn("Server did not accept the resize request: "
|
||||
+ msg);
|
||||
} else {
|
||||
this._resize(this._FBU.width, this._FBU.height);
|
||||
}
|
||||
|
|
80
docs/API.md
80
docs/API.md
|
@ -63,13 +63,9 @@ protocol stream.
|
|||
|
||||
### Events
|
||||
|
||||
[`updatestate`](#updatestate)
|
||||
- The `updatestate` event is fired when the connection state of the
|
||||
`RFB` object changes.
|
||||
|
||||
[`notification`](#notification)
|
||||
- The `notification` event is fired when the `RFB` usage has a
|
||||
message to display to the user.
|
||||
[`connect`](#connect)
|
||||
- The `connect` event is fired when the `RFB` object has completed
|
||||
the connection and handshaking with the server.
|
||||
|
||||
[`disconnect`](#disconnected)
|
||||
- The `disconnect` event is fired when the `RFB` object disconnects.
|
||||
|
@ -78,6 +74,10 @@ protocol stream.
|
|||
- The `credentialsrequired` event is fired when more credentials must
|
||||
be given to continue.
|
||||
|
||||
[`securityfailure`](#securityfailure)
|
||||
- The `securityfailure` event is fired when the security negotiation
|
||||
with the server fails.
|
||||
|
||||
[`clipboard`](#clipboard)
|
||||
- The `clipboard` event is fired when clipboard data is received from
|
||||
the server.
|
||||
|
@ -181,49 +181,19 @@ connection to a specified VNC server.
|
|||
- A `DOMString` specifying the ID to provide to any VNC repeater
|
||||
encountered.
|
||||
|
||||
#### updatestate
|
||||
#### connect
|
||||
|
||||
The `updatestate` event is fired after the noVNC connection state
|
||||
changes. The `detail` property is an `Object` containg the property
|
||||
`state` with the new connection state.
|
||||
|
||||
Here is a list of the states that are reported:
|
||||
|
||||
| connection state | description
|
||||
| ----------------- | ------------
|
||||
| `"connecting"` | starting to connect
|
||||
| `"connected"` | connected normally
|
||||
| `"disconnecting"` | starting to disconnect
|
||||
| `"disconnected"` | disconnected
|
||||
|
||||
Note that a `RFB` objects can not transition from the disconnected
|
||||
state in any way, a new instance of the object has to be created for
|
||||
new connections.
|
||||
|
||||
#### notification
|
||||
|
||||
The `notification` event is fired when the `RFB` object wants a message
|
||||
displayed to the user. The `detail` property is an `Object` containing
|
||||
the following properties:
|
||||
|
||||
| Property | Type | Description
|
||||
| --------- | ----------- | -----------
|
||||
| `message` | `DOMString` | The message to display
|
||||
| `level` | `DOMString` | The severity of the message
|
||||
|
||||
The following levels are currently defined:
|
||||
|
||||
- `"normal"`
|
||||
- `"warn"`
|
||||
- `"error"`
|
||||
The `connect` event is fired after all the handshaking with the server
|
||||
is completed and the connection is fully established. After this event
|
||||
the `RFB` object is ready to recieve graphics updates and to send input.
|
||||
|
||||
#### disconnect
|
||||
|
||||
The `disconnect` event is fired when the connection has been
|
||||
terminated. The `detail` property is an `Object` the optionally
|
||||
contains the property `reason`. `reason` is a `DOMString` specifying
|
||||
the reason in the event of an unexpected termination. `reason` will be
|
||||
omitted for a clean termination.
|
||||
terminated. The `detail` property is an `Object` that contains the
|
||||
property `clean`. `clean` is a `boolean` indicating if the termination
|
||||
was clean or not. In the event of an unexpected termination or an error
|
||||
`clean` will be set to false.
|
||||
|
||||
#### credentialsrequired
|
||||
|
||||
|
@ -232,6 +202,26 @@ credentials than were specified to [`RFB()`](#rfb-1). The `detail`
|
|||
property is an `Object` containing the property `types` which is an
|
||||
`Array` of `DOMString` listing the credentials that are required.
|
||||
|
||||
#### securityfailure
|
||||
|
||||
The `securityfailure` event is fired when the handshaking process with
|
||||
the server fails during the security negotiation step. The `detail`
|
||||
property is an `Object` containing the following properties:
|
||||
|
||||
| Property | Type | Description
|
||||
| -------- | ----------- | -----------
|
||||
| `status` | `long` | The failure status code
|
||||
| `reason` | `DOMString` | The **optional** reason for the failure
|
||||
|
||||
The property `status` corresponds to the
|
||||
[SecurityResult](https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult)
|
||||
status code in cases of failure. A status of zero will not be sent in
|
||||
this event since that indicates a successful security handshaking
|
||||
process. The optional property `reason` is provided by the server and
|
||||
thus the language of the string is not known. However most servers will
|
||||
probably send English strings. The server can choose to not send a
|
||||
reason and in these cases the `reason` property will be omitted.
|
||||
|
||||
#### clipboard
|
||||
|
||||
The `clipboard` event is fired when the server has sent clipboard data.
|
||||
|
|
|
@ -61,6 +61,7 @@ module.exports = function(config) {
|
|||
files: [
|
||||
{ pattern: 'vendor/sinon.js', included: false },
|
||||
{ pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false },
|
||||
{ pattern: 'app/localization.js', included: false },
|
||||
{ pattern: 'core/**/*.js', included: false },
|
||||
{ pattern: 'vendor/pako/**/*.js', included: false },
|
||||
{ pattern: 'tests/test.*.js', included: false },
|
||||
|
@ -90,6 +91,7 @@ module.exports = function(config) {
|
|||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'app/localization.js': ['babel'],
|
||||
'core/**/*.js': ['babel'],
|
||||
'tests/test.*.js': ['babel'],
|
||||
'tests/fake.*.js': ['babel'],
|
||||
|
|
|
@ -52,10 +52,6 @@ function enableUI() {
|
|||
encoding = VNC_frame_encoding;
|
||||
}
|
||||
|
||||
const notification = function (rfb, mesg, level, options) {
|
||||
document.getElementById('VNC_status').textContent = mesg;
|
||||
}
|
||||
|
||||
function IterationPlayer (iterations, frames, encoding) {
|
||||
this._iterations = iterations;
|
||||
|
||||
|
@ -72,7 +68,6 @@ function IterationPlayer (iterations, frames, encoding) {
|
|||
this.onfinish = function() {};
|
||||
this.oniterationfinish = function() {};
|
||||
this.rfbdisconnected = function() {};
|
||||
this.rfbnotification = function() {};
|
||||
}
|
||||
|
||||
IterationPlayer.prototype = {
|
||||
|
@ -87,7 +82,7 @@ IterationPlayer.prototype = {
|
|||
},
|
||||
|
||||
_nextIteration: function () {
|
||||
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this), this._notification.bind(this));
|
||||
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this));
|
||||
player.onfinish = this._iterationFinish.bind(this);
|
||||
|
||||
if (this._state !== 'running') { return; }
|
||||
|
@ -120,26 +115,17 @@ IterationPlayer.prototype = {
|
|||
this._nextIteration();
|
||||
},
|
||||
|
||||
_disconnected: function (rfb, reason, frame) {
|
||||
if (reason) {
|
||||
_disconnected: function (rfb, clean, frame) {
|
||||
if (!clean) {
|
||||
this._state = 'failed';
|
||||
}
|
||||
|
||||
var evt = new Event('rfbdisconnected');
|
||||
evt.reason = reason;
|
||||
evt.clean = clean;
|
||||
evt.frame = frame;
|
||||
|
||||
this.onrfbdisconnected(evt);
|
||||
},
|
||||
|
||||
_notification: function (rfb, msg, level, options) {
|
||||
var evt = new Event('rfbnotification');
|
||||
evt.message = msg;
|
||||
evt.level = level;
|
||||
evt.options = options;
|
||||
|
||||
this.onrfbnotification(evt);
|
||||
},
|
||||
};
|
||||
|
||||
function start() {
|
||||
|
@ -167,9 +153,6 @@ function start() {
|
|||
message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`);
|
||||
}
|
||||
};
|
||||
player.onrfbnotification = function (evt) {
|
||||
document.getElementById('VNC_status').textContent = evt.message;
|
||||
};
|
||||
player.onfinish = function (evt) {
|
||||
const iterTime = parseInt(evt.duration / evt.iterations, 10);
|
||||
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
|
||||
|
|
|
@ -44,12 +44,11 @@ if (setImmediate === undefined) {
|
|||
window.addEventListener("message", _onMessage);
|
||||
}
|
||||
|
||||
export default function RecordingPlayer (frames, encoding, disconnected, notification) {
|
||||
export default function RecordingPlayer (frames, encoding, disconnected) {
|
||||
this._frames = frames;
|
||||
this._encoding = encoding;
|
||||
|
||||
this._disconnected = disconnected;
|
||||
this._notification = notification;
|
||||
|
||||
if (this._encoding === undefined) {
|
||||
let frame = this._frames[0];
|
||||
|
@ -79,8 +78,8 @@ RecordingPlayer.prototype = {
|
|||
// initialize a new RFB
|
||||
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
|
||||
this._rfb.viewOnly = true;
|
||||
this._rfb.ondisconnected = this._handleDisconnect.bind(this);
|
||||
this._rfb.onnotification = this._notification;
|
||||
this._rfb.addEventListener("disconnect",
|
||||
this._handleDisconnect.bind(this));
|
||||
this._enablePlaybackMode();
|
||||
|
||||
// reset the frame index and timer
|
||||
|
@ -188,8 +187,8 @@ RecordingPlayer.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_handleDisconnect(rfb, reason) {
|
||||
_handleDisconnect(rfb, clean) {
|
||||
this._running = false;
|
||||
this._disconnected(rfb, reason, this._frame_index);
|
||||
this._disconnected(rfb, clean, this._frame_index);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* jshint expr: true */
|
||||
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
|
||||
import l10nGet, { l10n } from '../app/localization.js';
|
||||
|
||||
describe('Localization', function() {
|
||||
"use strict";
|
||||
|
||||
describe('language selection', function () {
|
||||
var origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.languages !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.languages = [];
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should use English by default', function() {
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use English if no user language matches', function() {
|
||||
window.navigator.languages = ["nl", "de"];
|
||||
l10n.setup(["es", "fr"]);
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use the most preferred user language', function() {
|
||||
window.navigator.languages = ["nl", "de", "fr"];
|
||||
l10n.setup(["es", "fr", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should prefer sub-languages languages', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt", "pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
it('should fall back to language "parents"', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["fr", "pt", "de"]);
|
||||
expect(l10n.language).to.equal('pt');
|
||||
});
|
||||
it('should not use specific language when user asks for a generic language', function() {
|
||||
window.navigator.languages = ["pt", "de"];
|
||||
l10n.setup(["fr", "pt-BR", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should handle underscore as a separator', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt_BR"]);
|
||||
expect(l10n.language).to.equal('pt_BR');
|
||||
});
|
||||
it('should handle difference in case', function() {
|
||||
window.navigator.languages = ["pt-br"];
|
||||
l10n.setup(["pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -68,11 +68,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
describe('#RFB', function () {
|
||||
it('should set the current state to "connecting"', function () {
|
||||
var client = new RFB(document.createElement('canvas'), 'wss://host:8675');
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("updatestate", spy);
|
||||
client._rfb_connection_state = '';
|
||||
this.clock.tick();
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.state).to.equal('connecting');
|
||||
expect(client._rfb_connection_state).to.equal('connecting');
|
||||
});
|
||||
|
||||
it('should actually connect to the websocket', function () {
|
||||
|
@ -90,13 +88,15 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
client = make_rfb();
|
||||
});
|
||||
|
||||
it('should set the current state to "disconnecting"', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("updatestate", spy);
|
||||
it('should go to state "disconnecting" before "disconnected"', function () {
|
||||
sinon.spy(client, '_updateConnectionState');
|
||||
client.disconnect();
|
||||
expect(spy).to.have.been.calledTwice;
|
||||
expect(spy.args[0][0].detail.state).to.equal('disconnecting');
|
||||
expect(spy.args[1][0].detail.state).to.equal('disconnected');
|
||||
expect(client._updateConnectionState).to.have.been.calledTwice;
|
||||
expect(client._updateConnectionState.getCall(0).args[0])
|
||||
.to.equal('disconnecting');
|
||||
expect(client._updateConnectionState.getCall(1).args[0])
|
||||
.to.equal('disconnected');
|
||||
expect(client._rfb_connection_state).to.equal('disconnected');
|
||||
});
|
||||
|
||||
it('should unregister error event handler', function () {
|
||||
|
@ -320,13 +320,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
expect(client._disconnTimer).to.be.null;
|
||||
});
|
||||
|
||||
it('should call the updateState callback', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("updatestate", spy);
|
||||
client._updateConnectionState('disconnecting');
|
||||
expect(spy.args[0][0].detail.state).to.equal('disconnecting');
|
||||
});
|
||||
|
||||
it('should set the rfb_connection_state', function () {
|
||||
client._rfb_connection_state = 'disconnecting';
|
||||
client._updateConnectionState('disconnected');
|
||||
|
@ -340,16 +333,23 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
});
|
||||
|
||||
it('should ignore state changes to the same state', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("updatestate", spy);
|
||||
client._rfb_connection_state = 'connecting';
|
||||
client._updateConnectionState('connecting');
|
||||
expect(spy).to.not.have.been.called;
|
||||
var connectSpy = sinon.spy();
|
||||
var disconnectSpy = sinon.spy();
|
||||
client.addEventListener("connect", connectSpy);
|
||||
client.addEventListener("disconnect", disconnectSpy);
|
||||
|
||||
client._rfb_connection_state = 'connected';
|
||||
client._updateConnectionState('connected');
|
||||
expect(connectSpy).to.not.have.been.called;
|
||||
|
||||
client._rfb_connection_state = 'disconnected';
|
||||
client._updateConnectionState('disconnected');
|
||||
expect(disconnectSpy).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should ignore illegal state changes', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("updatestate", spy);
|
||||
client.addEventListener("disconnect", spy);
|
||||
client._rfb_connection_state = 'connected';
|
||||
client._updateConnectionState('disconnected');
|
||||
expect(client._rfb_connection_state).to.not.equal('disconnected');
|
||||
|
@ -377,53 +377,60 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
expect(client._rfb_connection_state).to.equal('disconnected');
|
||||
});
|
||||
|
||||
it('should set disconnect_reason', function () {
|
||||
it('should set clean_disconnect variable', function () {
|
||||
client._rfb_clean_disconnect = true;
|
||||
client._rfb_connection_state = 'connected';
|
||||
client._fail('a reason');
|
||||
expect(client._rfb_disconnect_reason).to.equal('a reason');
|
||||
client._fail();
|
||||
expect(client._rfb_clean_disconnect).to.be.false;
|
||||
});
|
||||
|
||||
it('should not include details in disconnect_reason', function () {
|
||||
client._rfb_connection_state = 'connected';
|
||||
client._fail('a reason', 'details');
|
||||
expect(client._rfb_disconnect_reason).to.equal('a reason');
|
||||
});
|
||||
|
||||
it('should result in disconnect callback with message when reason given', function () {
|
||||
it('should result in disconnect event with clean set to false', function () {
|
||||
client._rfb_connection_state = 'connected';
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("disconnect", spy);
|
||||
client._fail('a reason');
|
||||
client._fail();
|
||||
this.clock.tick(2000);
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.reason).to.equal('a reason');
|
||||
expect(spy.args[0][0].detail.clean).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#_notification', function () {
|
||||
var client;
|
||||
beforeEach(function () { client = make_rfb(); });
|
||||
|
||||
it('should call the notification callback', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("notification", spy);
|
||||
client._notification('notify!', 'warn');
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.message).to.equal('notify!');
|
||||
expect(spy.args[0][0].detail.level).to.equal('warn');
|
||||
});
|
||||
|
||||
it('should not call the notification callback when level is invalid', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("notification", spy);
|
||||
client._notification('notify!', 'invalid');
|
||||
expect(spy).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Connection States', function () {
|
||||
describe('connecting', function () {
|
||||
it('should open the websocket connection', function () {
|
||||
var client = new RFB(document.createElement('canvas'),
|
||||
'ws://HOST:8675/PATH');
|
||||
sinon.spy(client._sock, 'open');
|
||||
this.clock.tick();
|
||||
expect(client._sock.open).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('connected', function () {
|
||||
var client;
|
||||
beforeEach(function () {
|
||||
client = make_rfb();
|
||||
});
|
||||
|
||||
it('should result in a connect event if state becomes connected', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("connect", spy);
|
||||
client._rfb_connection_state = 'connecting';
|
||||
client._updateConnectionState('connected');
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not result in a connect event if the state is not "connected"', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("connect", spy);
|
||||
client._sock._websocket.open = function () {}; // explicitly don't call onopen
|
||||
client._updateConnectionState('connecting');
|
||||
expect(spy).to.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('disconnecting', function () {
|
||||
var client;
|
||||
beforeEach(function () {
|
||||
|
@ -459,17 +466,16 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
var client;
|
||||
beforeEach(function () { client = make_rfb(); });
|
||||
|
||||
it('should call the disconnect callback if the state is "disconnected"', function () {
|
||||
it('should result in a disconnect event if state becomes "disconnected"', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("disconnect", spy);
|
||||
client._rfb_connection_state = 'disconnecting';
|
||||
client._rfb_disconnect_reason = "error";
|
||||
client._updateConnectionState('disconnected');
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.reason).to.equal("error");
|
||||
expect(spy.args[0][0].detail.clean).to.be.true;
|
||||
});
|
||||
|
||||
it('should not call the disconnect callback if the state is not "disconnected"', function () {
|
||||
it('should not result in a disconnect event if the state is not "disconnected"', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("disconnect", spy);
|
||||
client._sock._websocket.close = function () {}; // explicitly don't call onclose
|
||||
|
@ -477,7 +483,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
expect(spy).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should call the disconnect callback without msg when no reason given', function () {
|
||||
it('should result in a disconnect event without msg when no reason given', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("disconnect", spy);
|
||||
client._rfb_connection_state = 'disconnecting';
|
||||
|
@ -486,19 +492,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0].length).to.equal(1);
|
||||
});
|
||||
|
||||
it('should call the updateState callback before the disconnect callback', function () {
|
||||
var updateStateSpy = sinon.spy();
|
||||
var disconnectSpy = sinon.spy();
|
||||
client.addEventListener("disconnect", disconnectSpy);
|
||||
client.addEventListener("updatestate", updateStateSpy);
|
||||
client._rfb_connection_state = 'disconnecting';
|
||||
client._updateConnectionState('disconnected');
|
||||
expect(updateStateSpy.calledBefore(disconnectSpy)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
// NB(directxman12): Connected does *nothing* in updateConnectionState
|
||||
});
|
||||
|
||||
describe('Protocol Initialization States', function () {
|
||||
|
@ -653,7 +647,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
|
||||
expect(client._fail).to.have.been.calledOnce;
|
||||
expect(client._fail).to.have.been.calledWith(
|
||||
'Error while negotiating with server','Security failure: whoops');
|
||||
'Security negotiation failed on no security types (reason: whoops)');
|
||||
});
|
||||
|
||||
it('should transition to the Authentication state and continue on successful negotiation', function () {
|
||||
|
@ -688,7 +682,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
sinon.spy(client, '_fail');
|
||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||
expect(client._fail).to.have.been.calledWith(
|
||||
'Authentication failure', 'Whoopsies');
|
||||
'Security negotiation failed on authentication scheme (reason: Whoopsies)');
|
||||
});
|
||||
|
||||
it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
|
||||
|
@ -909,14 +903,53 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
|
||||
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||
expect(client._fail).to.have.been.calledWith(
|
||||
'Authentication failure', 'whoops');
|
||||
'Security negotiation failed on security result (reason: whoops)');
|
||||
});
|
||||
|
||||
it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
|
||||
sinon.spy(client, '_fail');
|
||||
client._rfb_version = 3.7;
|
||||
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
|
||||
expect(client._fail).to.have.been.calledWith('Authentication failure');
|
||||
expect(client._fail).to.have.been.calledWith(
|
||||
'Security handshake failed');
|
||||
});
|
||||
|
||||
it('should result in securityfailure event when receiving a non zero status', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("securityfailure", spy);
|
||||
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.status).to.equal(2);
|
||||
});
|
||||
|
||||
it('should include reason when provided in securityfailure event', function () {
|
||||
client._rfb_version = 3.8;
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("securityfailure", spy);
|
||||
var failure_data = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,
|
||||
32, 102, 97, 105, 108, 117, 114, 101];
|
||||
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||
expect(spy.args[0][0].detail.status).to.equal(1);
|
||||
expect(spy.args[0][0].detail.reason).to.equal('such failure');
|
||||
});
|
||||
|
||||
it('should not include reason when length is zero in securityfailure event', function () {
|
||||
client._rfb_version = 3.9;
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("securityfailure", spy);
|
||||
var failure_data = [0, 0, 0, 1, 0, 0, 0, 0];
|
||||
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||
expect(spy.args[0][0].detail.status).to.equal(1);
|
||||
expect('reason' in spy.args[0][0].detail).to.be.false;
|
||||
});
|
||||
|
||||
it('should not include reason in securityfailure event for version < 3.8', function () {
|
||||
client._rfb_version = 3.6;
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("securityfailure", spy);
|
||||
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 2]));
|
||||
expect(spy.args[0][0].detail.status).to.equal(2);
|
||||
expect('reason' in spy.args[0][0].detail).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1589,14 +1622,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
});
|
||||
|
||||
describe('XVP Message Handling', function () {
|
||||
it('should send a notification on XVP_FAIL', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("notification", spy);
|
||||
client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0]));
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][0].detail.message).to.equal('XVP Operation Failed');
|
||||
});
|
||||
|
||||
it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
|
||||
var spy = sinon.spy();
|
||||
client.addEventListener("capabilities", spy);
|
||||
|
|
|
@ -4,7 +4,6 @@ var assert = chai.assert;
|
|||
var expect = chai.expect;
|
||||
|
||||
import * as Log from '../core/util/logging.js';
|
||||
import l10nGet, { l10n } from '../core/util/localization.js';
|
||||
|
||||
import sinon from '../vendor/sinon.js';
|
||||
|
||||
|
@ -63,72 +62,6 @@ describe('Utils', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('language selection', function () {
|
||||
var origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.languages !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.languages = [];
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should use English by default', function() {
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use English if no user language matches', function() {
|
||||
window.navigator.languages = ["nl", "de"];
|
||||
l10n.setup(["es", "fr"]);
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use the most preferred user language', function() {
|
||||
window.navigator.languages = ["nl", "de", "fr"];
|
||||
l10n.setup(["es", "fr", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should prefer sub-languages languages', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt", "pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
it('should fall back to language "parents"', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["fr", "pt", "de"]);
|
||||
expect(l10n.language).to.equal('pt');
|
||||
});
|
||||
it('should not use specific language when user asks for a generic language', function() {
|
||||
window.navigator.languages = ["pt", "de"];
|
||||
l10n.setup(["fr", "pt-BR", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should handle underscore as a separator', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt_BR"]);
|
||||
expect(l10n.language).to.equal('pt_BR');
|
||||
});
|
||||
it('should handle difference in case', function() {
|
||||
window.navigator.languages = ["pt-br"];
|
||||
l10n.setup(["pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(directxman12): test the conf_default and conf_defaults methods
|
||||
// TODO(directxman12): test decodeUTF8
|
||||
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
|
||||
|
|
|
@ -85,10 +85,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
notification = function (rfb, mesg, level, options) {
|
||||
document.getElementById('VNC_status').textContent = mesg;
|
||||
}
|
||||
|
||||
function do_test() {
|
||||
document.getElementById('startButton').value = "Running";
|
||||
document.getElementById('startButton').disabled = true;
|
||||
|
|
|
@ -147,50 +147,27 @@
|
|||
document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level;
|
||||
document.getElementById('noVNC_status').textContent = text;
|
||||
}
|
||||
function updateState(e) {
|
||||
var cad = document.getElementById('sendCtrlAltDelButton');
|
||||
switch (e.detail.state) {
|
||||
case 'connecting':
|
||||
status("Connecting", "normal");
|
||||
break;
|
||||
case 'connected':
|
||||
doneInitialResize = false;
|
||||
if (WebUtil.getConfigVar('encrypt',
|
||||
(window.location.protocol === "https:"))) {
|
||||
status("Connected (encrypted) to " +
|
||||
desktopName, "normal");
|
||||
} else {
|
||||
status("Connected (unencrypted) to " +
|
||||
desktopName, "normal");
|
||||
}
|
||||
break;
|
||||
case 'disconnecting':
|
||||
status("Disconnecting", "normal");
|
||||
break;
|
||||
case 'disconnected':
|
||||
status("Disconnected", "normal");
|
||||
break;
|
||||
default:
|
||||
status(e.detail.state, "warn");
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.detail.state === 'connected') {
|
||||
cad.disabled = false;
|
||||
function connected(e) {
|
||||
document.getElementById('sendCtrlAltDelButton').disabled = false;
|
||||
doneInitialResize = false;
|
||||
if (WebUtil.getConfigVar('encrypt',
|
||||
(window.location.protocol === "https:"))) {
|
||||
status("Connected (encrypted) to " + desktopName, "normal");
|
||||
} else {
|
||||
cad.disabled = true;
|
||||
updatePowerButtons();
|
||||
status("Connected (unencrypted) to " + desktopName, "normal");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
function disconnect(e) {
|
||||
if (typeof(e.detail.reason) !== 'undefined') {
|
||||
status(e.detail.reason, "error");
|
||||
function disconnected(e) {
|
||||
document.getElementById('sendCtrlAltDelButton').disabled = true;
|
||||
updatePowerButtons();
|
||||
if (e.detail.clean) {
|
||||
status("Disconnected", "normal");
|
||||
} else {
|
||||
status("Something went wrong, connection is closed", "error");
|
||||
}
|
||||
}
|
||||
function notification(e) {
|
||||
status(e.detail.message, e.detail.level);
|
||||
}
|
||||
|
||||
window.onresize = function () {
|
||||
// When the window has been resized, wait until the size remains
|
||||
|
@ -249,6 +226,8 @@
|
|||
|
||||
(function() {
|
||||
|
||||
status("Connecting", "normal");
|
||||
|
||||
if ((!host) || (!port)) {
|
||||
status('Must specify host and port in URL', 'error');
|
||||
}
|
||||
|
@ -272,10 +251,9 @@
|
|||
{ repeaterID: WebUtil.getConfigVar('repeaterID', ''),
|
||||
shared: WebUtil.getConfigVar('shared', true),
|
||||
credentials: { password: password } });
|
||||
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
|
||||
rfb.addEventListener("notification", notification);
|
||||
rfb.addEventListener("updatestate", updateState);
|
||||
rfb.addEventListener("disconnect", disconnect);
|
||||
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
|
||||
rfb.addEventListener("connect", connected);
|
||||
rfb.addEventListener("disconnect", disconnected);
|
||||
rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
|
||||
rfb.addEventListener("credentialsrequired", credentials);
|
||||
rfb.addEventListener("desktopname", updateDesktopName);
|
||||
|
|
Loading…
Reference in New Issue