Remove unnecessary substates of disconnected

The states 'loaded', 'failed' and 'fatal' were all variations of the
'disconnected' state. Removing these states allows us to get rid of
many ugly workarounds and special cases. Error messages to the UI can
now instead be delivered via a new onDisconnected callback.
This commit is contained in:
Samuel Mannehed 2016-09-18 00:52:25 +02:00
parent c2a4d3ef86
commit 3bb12056b1
4 changed files with 317 additions and 255 deletions

View File

@ -36,7 +36,8 @@ var UI;
UI = {
rfb_state: 'loaded',
connected: false,
desktopName: "",
resizeTimeout: null,
statusTimeout: null,
@ -340,15 +341,16 @@ var UI;
UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'),
'onNotification': UI.notification,
'onUpdateState': UI.updateState,
'onDisconnected': UI.disconnectFinished,
'onPasswordRequired': UI.passwordRequired,
'onXvpInit': UI.updateXvpButton,
'onClipboard': UI.clipboardReceive,
'onFBUComplete': UI.initialResize,
'onFBResize': UI.updateViewDrag,
'onDesktopName': UI.updateDocumentTitle});
'onDesktopName': UI.updateDesktopName});
return true;
} catch (exc) {
UI.updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
UI.showStatus('Unable to create RFB client -- ' + exc, 'error');
return false;
}
},
@ -359,53 +361,56 @@ var UI;
* VISUAL
* ------v------*/
updateState: function(rfb, state, oldstate, msg) {
UI.rfb_state = state;
if (typeof(msg) !== 'undefined') {
updateState: function(rfb, state, oldstate) {
switch (state) {
case 'failed':
case 'fatal':
// zero means no timeout
UI.showStatus(msg, 'error', 0);
case 'connecting':
UI.showStatus("Connecting");
break;
case 'connected':
/* falls through */
UI.connected = true;
if (rfb && rfb.get_encrypt()) {
UI.showStatus("Connected (encrypted) to " +
UI.desktopName);
} else {
UI.showStatus("Connected (unencrypted) to " +
UI.desktopName);
}
break;
case 'disconnecting':
UI.showStatus("Disconnecting");
break;
case 'disconnected':
case 'loaded':
UI.showStatus(msg, 'normal');
UI.connected = false;
UI.showStatus("Disconnected");
break;
default:
UI.showStatus(msg, 'warn');
UI.showStatus("Invalid state", 'error');
break;
}
}
UI.updateVisualState();
},
// Disable/enable controls depending on connection state
updateVisualState: function() {
var connected = UI.rfb && UI.rfb_state === 'connected';
//Util.Debug(">> updateVisualState");
document.getElementById('noVNC_setting_encrypt').disabled = connected;
document.getElementById('noVNC_setting_true_color').disabled = connected;
document.getElementById('noVNC_setting_encrypt').disabled = UI.connected;
document.getElementById('noVNC_setting_true_color').disabled = UI.connected;
if (Util.browserSupportsCursorURIs()) {
document.getElementById('noVNC_setting_cursor').disabled = connected;
document.getElementById('noVNC_setting_cursor').disabled = UI.connected;
} else {
UI.updateSetting('cursor', !UI.isTouchDevice);
document.getElementById('noVNC_setting_cursor').disabled = true;
}
UI.enableDisableViewClip();
document.getElementById('noVNC_setting_resize').disabled = connected;
document.getElementById('noVNC_setting_shared').disabled = connected;
document.getElementById('noVNC_setting_view_only').disabled = connected;
document.getElementById('noVNC_setting_path').disabled = connected;
document.getElementById('noVNC_setting_repeaterID').disabled = connected;
document.getElementById('noVNC_setting_resize').disabled = UI.connected;
document.getElementById('noVNC_setting_shared').disabled = UI.connected;
document.getElementById('noVNC_setting_view_only').disabled = UI.connected;
document.getElementById('noVNC_setting_path').disabled = UI.connected;
document.getElementById('noVNC_setting_repeaterID').disabled = UI.connected;
if (connected) {
if (UI.connected) {
document.documentElement.classList.add("noVNC_connected");
UI.updateViewClip();
UI.setMouseButton(1);
@ -425,18 +430,6 @@ var UI;
document.getElementById('noVNC_password_dlg')
.classList.remove('noVNC_open');
switch (UI.rfb_state) {
case 'fatal':
case 'failed':
case 'disconnected':
UI.openConnectPanel();
break;
case 'loaded':
break;
default:
break;
}
//Util.Debug("<< updateVisualState");
},
@ -949,7 +942,8 @@ var UI;
}
if ((!host) || (!port)) {
throw new Error("Must set host and port");
UI.showStatus("Must set host and port", 'error');
return;
}
if (!UI.initRFB()) return;
@ -974,6 +968,13 @@ var UI;
// Don't display the connection settings until we're actually disconnected
},
disconnectFinished: function (rfb, reason) {
if (typeof reason !== 'undefined') {
UI.showStatus(reason, 'error');
}
UI.openConnectPanel();
},
/* ------^-------
* /CONNECTION
* ==============
@ -992,7 +993,7 @@ var UI;
if (typeof msg === 'undefined') {
msg = "Password is required";
}
UI.updateState(null, "warning", null, msg);
UI.showStatus(msg, "warning");
},
setPassword: function() {
@ -1062,7 +1063,7 @@ var UI;
var screen = UI.screenSize();
if (screen && UI.rfb_state === 'connected' && UI.rfb.get_display()) {
if (screen && UI.connected && UI.rfb.get_display()) {
var display = UI.rfb.get_display();
var resizeMode = UI.getSetting('resize');
@ -1187,7 +1188,6 @@ var UI;
// Handle special cases where clipping is forced on/off or locked
enableDisableViewClip: function() {
var resizeSetting = document.getElementById('noVNC_setting_resize');
var connected = UI.rfb && UI.rfb_state === 'connected';
if (UI.isSafari) {
// Safari auto-hides the scrollbars which makes them
@ -1209,9 +1209,11 @@ var UI;
} else if (document.body.msRequestFullscreen && UI.rememberedClip !== null) {
// Restore view clip to what it was before fullscreen on IE
UI.setViewClip(UI.rememberedClipSetting);
document.getElementById('noVNC_setting_clip').disabled = connected || UI.isTouchDevice;
document.getElementById('noVNC_setting_clip').disabled =
UI.connected || UI.isTouchDevice;
} else {
document.getElementById('noVNC_setting_clip').disabled = connected || UI.isTouchDevice;
document.getElementById('noVNC_setting_clip').disabled =
UI.connected || UI.isTouchDevice;
if (UI.isTouchDevice) {
UI.setViewClip(true);
}
@ -1243,7 +1245,7 @@ var UI;
updateViewDrag: function() {
var clipping = false;
if (UI.rfb_state !== 'connected') return;
if (!UI.connected) return;
// Check if viewport drag is possible. It is only possible
// if the remote display is clipping the client display.
@ -1537,8 +1539,9 @@ var UI;
UI.rfb.get_mouse().set_focused(true);
},
updateDesktopName: function(rfb, name) {
UI.desktopName = name;
// Display the desktop name in the document title
updateDocumentTitle: function(rfb, name) {
document.title = name + " - noVNC";
},

View File

@ -35,11 +35,12 @@
this._rfb_password = '';
this._rfb_path = '';
this._rfb_connection_state = 'disconnected';
this._rfb_connection_state = '';
this._rfb_init_state = '';
this._rfb_version = 0;
this._rfb_max_version = 3.8;
this._rfb_auth_scheme = '';
this._rfb_disconnect_reason = "";
this._rfb_tightvnc = false;
this._rfb_xvp_ver = 0;
@ -158,8 +159,9 @@
'viewportDrag': false, // Move the viewport on mouse drags
// Callback functions
'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change
'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI
'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished
'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required
'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
'onBell': function () { }, // onBell(rfb): RFB Bell message received
@ -225,12 +227,11 @@
}
switch (this._rfb_connection_state) {
case 'disconnecting':
this._updateConnectionState('disconnected', 'VNC disconnected' + msg);
this._updateConnectionState('disconnected');
break;
case 'connecting':
this._fail('Failed to connect to server' + msg);
break;
case 'failed':
case 'disconnected':
Util.Error("Received onclose while disconnected" + msg);
break;
@ -245,10 +246,10 @@
});
this._init_vars();
this._cleanupSocket();
var rmode = this._display.get_render_mode();
Util.Info("Using native WebSockets");
this._updateConnectionState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
Util.Info("Using native WebSockets, render mode: " + rmode);
Util.Debug("<< RFB.constructor");
};
@ -272,7 +273,7 @@
},
disconnect: function () {
this._updateConnectionState('disconnecting', 'Disconnecting');
this._updateConnectionState('disconnecting');
this._sock.off('error');
this._sock.off('message');
this._sock.off('open');
@ -407,7 +408,7 @@
}
},
_cleanupSocket: function (state) {
_cleanupSocket: function () {
if (this._msgTimer) {
clearInterval(this._msgTimer);
this._msgTimer = null;
@ -416,10 +417,8 @@
if (this._display && this._display.get_context()) {
this._keyboard.ungrab();
this._mouse.ungrab();
if (state !== 'connecting' && state !== 'loaded') {
this._display.defaultCursor();
}
if (Util.get_logging() !== 'debug' || state === 'loaded') {
if (Util.get_logging() !== 'debug') {
// Show noVNC logo on load and when disconnected, unless in
// debug mode
this._display.clear();
@ -431,47 +430,27 @@
/*
* Connection states:
* loaded - page load, equivalent to disconnected
* disconnected - idle state
* connecting
* connected
* disconnecting
* failed - abnormal disconnect
* fatal - failed to load page, or fatal error
* disconnected - permanent state
*/
_updateConnectionState: function (state, statusMsg) {
_updateConnectionState: function (state) {
var oldstate = this._rfb_connection_state;
if (state === oldstate) {
// Already here, ignore
Util.Debug("Already in state '" + state + "', ignoring");
return;
}
// The 'disconnected' state is permanent for each RFB object
if (oldstate === 'disconnected') {
Util.Error("Tried changing state of a disconnected RFB object");
return;
}
this._rfb_connection_state = state;
/*
* These are disconnected states. A previous connect may
* asynchronously cause a connection so make sure we are closed.
*/
if (state in {'disconnected': 1, 'loaded': 1, 'connecting': 1,
'disconnecting': 1, 'failed': 1, 'fatal': 1}) {
this._cleanupSocket(state);
}
if (oldstate === 'fatal') {
Util.Error('Fatal error, cannot continue');
}
if (typeof(statusMsg) !== 'undefined') {
var cmsg = " Msg: " + statusMsg;
if (state === 'failed' || state === 'fatal') {
Util.Error(cmsg);
} else {
Util.Warn(cmsg);
}
}
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
Util.Debug(smsg);
@ -482,60 +461,73 @@
this._sock.off('close'); // make sure we don't get a double event
}
this._onUpdateState(this, state, oldstate);
switch (state) {
case 'connected':
if (oldstate === 'disconnected' || oldstate === 'failed') {
Util.Error("Invalid transition from 'disconnected' or 'failed' to 'connected'");
if (oldstate !== 'connecting') {
Util.Error("Bad transition to connected state, " +
"previous connection state: " + oldstate);
return;
}
break;
case 'disconnected':
if (oldstate !== 'disconnecting') {
Util.Error("Bad transition to disconnected state, " +
"previous connection state: " + oldstate);
return;
}
if (this._rfb_disconnect_reason !== "") {
this._onDisconnected(this, this._rfb_disconnect_reason);
} else {
// No reason means clean disconnect
this._onDisconnected(this);
}
break;
case 'connecting':
this._init_vars();
// WebSocket.onopen transitions to the RFB init states
this._connect();
// WebSocket.onopen transitions to 'ProtocolVersion'
break;
case 'disconnecting':
// WebSocket.onclose transitions to 'disconnected'
this._cleanupSocket();
this._disconnTimer = setTimeout(function () {
this._fail("Disconnect timeout");
this._rfb_disconnect_reason = "Disconnect timeout";
this._updateConnectionState('disconnected');
}.bind(this), this._disconnectTimeout * 1000);
this._print_stats();
// WebSocket.onclose transitions to 'disconnected'
break;
case 'failed':
if (oldstate === 'disconnected') {
Util.Error("Invalid transition from 'disconnected' to 'failed'");
} else if (oldstate === 'connected') {
Util.Error("Error while connected.");
} else if (oldstate === 'init') {
Util.Error("Error while initializing.");
}
break;
default:
// No state change action to take
}
if (oldstate === 'failed' && state === 'disconnected') {
// do disconnect action, but stay in failed state and
// keep the previous status message
this._rfb_connection_state = 'failed';
this._onUpdateState(this, state, oldstate);
} else {
this._onUpdateState(this, state, oldstate, statusMsg);
}
// Make sure we transition to disconnected
if (state === 'failed') {
this._updateConnectionState('disconnected');
Util.Error("Unknown connection state: " + state);
return;
}
},
_fail: function (msg) {
this._updateConnectionState('failed', msg);
switch (this._rfb_connection_state) {
case 'disconnecting':
Util.Error("Error while disconnecting: " + msg);
break;
case 'connected':
Util.Error("Error while connected: " + msg);
break;
case 'connecting':
Util.Error("Error while connecting: " + msg);
break;
default:
Util.Error("RFB error: " + msg);
break;
}
this._rfb_disconnect_reason = msg;
this._updateConnectionState('disconnecting');
return false;
},
@ -574,7 +566,6 @@
switch (this._rfb_connection_state) {
case 'disconnected':
case 'failed':
Util.Error("Got data while disconnected");
break;
case 'connected':
@ -1057,11 +1048,7 @@
this._timing.fbu_rt_start = (new Date()).getTime();
this._timing.pixels = 0;
if (this._encrypt) {
this._updateConnectionState('connected', 'Connected (encrypted) to: ' + this._fb_name);
} else {
this._updateConnectionState('connected', 'Connected (unencrypted) to: ' + this._fb_name);
}
this._updateConnectionState('connected');
return true;
},
@ -1376,8 +1363,9 @@
['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
// Callback functions
['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change
['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI
['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished
['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required
['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received

View File

@ -73,24 +73,22 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._updateConnectionState).to.have.been.calledWith('connecting');
});
it('should fail if we are missing a host', function () {
sinon.spy(client, '_fail');
it('should not try to connect if we are missing a host', function () {
client._fail = sinon.spy();
client._rfb_connection_state = '';
client.connect(undefined, 8675);
expect(client._fail).to.have.been.calledOnce;
expect(client._updateConnectionState).to.not.have.been.called;
expect(client._rfb_connection_state).to.equal('');
});
it('should fail if we are missing a port', function () {
sinon.spy(client, '_fail');
it('should not try to connect if we are missing a port', function () {
client._fail = sinon.spy();
client._rfb_connection_state = '';
client.connect('abc');
expect(client._fail).to.have.been.calledOnce;
});
it('should not update the state if we are missing a host or port', function () {
sinon.spy(client, '_fail');
client.connect('abc');
expect(client._fail).to.have.been.calledOnce;
expect(client._updateConnectionState).to.have.been.calledOnce;
expect(client._updateConnectionState).to.have.been.calledWith('failed');
expect(client._updateConnectionState).to.not.have.been.called;
expect(client._rfb_connection_state).to.equal('');
});
});
@ -337,6 +335,76 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(spy).to.not.have.been.called;
expect(client._disconnTimer).to.be.null;
});
it('should call the updateState callback', function () {
client.set_onUpdateState(sinon.spy());
client._updateConnectionState('a specific state');
var spy = client.get_onUpdateState();
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][1]).to.equal('a specific state');
});
it('should set the rfb_connection_state', function () {
client._updateConnectionState('a specific state');
expect(client._rfb_connection_state).to.equal('a specific state');
});
it('should not change the state when we are disconnected', function () {
client._rfb_connection_state = 'disconnected';
client._updateConnectionState('a specific state');
expect(client._rfb_connection_state).to.not.equal('a specific state');
});
it('should ignore state changes to the same state', function () {
client.set_onUpdateState(sinon.spy());
client._rfb_connection_state = 'a specific state';
client._updateConnectionState('a specific state');
var spy = client.get_onUpdateState();
expect(spy).to.not.have.been.called;
});
});
describe('#_fail', function () {
var client;
beforeEach(function () {
this.clock = sinon.useFakeTimers();
client = make_rfb();
client.connect('host', 8675);
});
afterEach(function () {
this.clock.restore();
});
it('should close the WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._fail();
expect(client._sock.close).to.have.been.calledOnce;
});
it('should transition to disconnected', function () {
sinon.spy(client, '_updateConnectionState');
client._fail();
this.clock.tick(2000);
expect(client._updateConnectionState).to.have.been.called;
expect(client._rfb_connection_state).to.equal('disconnected');
});
it('should set disconnect_reason', function () {
client._fail('a reason');
expect(client._rfb_disconnect_reason).to.equal('a reason');
});
it('should result in disconnect callback with message when reason given', function () {
client.set_onDisconnected(sinon.spy());
client._fail('a reason');
var spy = client.get_onDisconnected();
this.clock.tick(2000);
expect(spy).to.have.been.calledOnce;
expect(spy.args[0].length).to.equal(2);
expect(spy.args[0][1]).to.equal('a reason');
});
});
describe('#_notification', function () {
@ -359,35 +427,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(spy).to.not.have.been.called;
});
});
});
describe('Connection States', function () {
describe('loaded', function () {
var client;
beforeEach(function () {
client = make_rfb();
client._rfb_connection_state = 'disconnected';
});
it('should close any open WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('loaded');
expect(client._sock.close).to.have.been.calledOnce;
});
});
describe('disconnected', function () {
var client;
beforeEach(function () { client = make_rfb(); });
it('should close any open WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('disconnected');
expect(client._sock.close).to.have.been.calledOnce;
});
});
describe('connecting', function () {
var client;
beforeEach(function () { client = make_rfb(); });
@ -427,12 +469,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._updateConnectionState('connecting');
expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH');
});
it('should attempt to close the websocket before we open an new one', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('connecting');
expect(client._sock.close).to.have.been.calledOnce;
});
});
describe('disconnecting', function () {
@ -447,11 +483,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
this.clock.restore();
});
it('should fail if we do not call Websock.onclose within the disconnection timeout', function () {
it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () {
sinon.spy(client, '_updateConnectionState');
client._sock._websocket.close = function () {}; // explicitly don't call onclose
client._updateConnectionState('disconnecting');
this.clock.tick(client.get_disconnectTimeout() * 1000);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._updateConnectionState).to.have.been.calledTwice;
expect(client._rfb_disconnect_reason).to.not.equal("");
expect(client._rfb_connection_state).to.equal("disconnected");
});
it('should not fail if Websock.onclose gets called within the disconnection timeout', function () {
@ -465,51 +504,49 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should close the WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('disconnecting');
expect(client._sock.close).to.have.been.calledTwice; // once on loaded, once on disconnect
expect(client._sock.close).to.have.been.calledOnce;
});
});
describe('failed', function () {
var client;
beforeEach(function () {
this.clock = sinon.useFakeTimers();
client = make_rfb();
client.connect('host', 8675);
});
afterEach(function () {
this.clock.restore();
});
it('should close the WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('failed');
expect(client._sock.close).to.have.been.called;
});
it('should transition to disconnected but stay in failed state', function () {
client.set_onUpdateState(sinon.spy());
client._updateConnectionState('failed');
this.clock.tick(50);
expect(client._rfb_connection_state).to.equal('failed');
var onUpdateState = client.get_onUpdateState();
expect(onUpdateState).to.have.been.called;
// it should be specifically the last call
expect(onUpdateState.args[onUpdateState.args.length - 1][1]).to.equal('disconnected');
expect(onUpdateState.args[onUpdateState.args.length - 1][2]).to.equal('failed');
});
});
describe('fatal', function () {
describe('disconnected', function () {
var client;
beforeEach(function () { client = make_rfb(); });
it('should close any open WebSocket connection', function () {
sinon.spy(client._sock, 'close');
client._updateConnectionState('fatal');
expect(client._sock.close).to.have.been.calledOnce;
it('should call the disconnect callback if the state is "disconnected"', function () {
client.set_onDisconnected(sinon.spy());
client._rfb_connection_state = 'disconnecting';
client._rfb_disconnect_reason = "error";
client._updateConnectionState('disconnected');
var spy = client.get_onDisconnected();
expect(spy).to.have.been.calledOnce;
expect(spy.args[0][1]).to.equal("error");
});
it('should not call the disconnect callback if the state is not "disconnected"', function () {
client.set_onDisconnected(sinon.spy());
client._updateConnectionState('disconnecting');
var spy = client.get_onDisconnected();
expect(spy).to.not.have.been.called;
});
it('should call the disconnect callback without msg when no reason given', function () {
client.set_onDisconnected(sinon.spy());
client._rfb_connection_state = 'disconnecting';
client._rfb_disconnect_reason = "";
client._updateConnectionState('disconnected');
var spy = client.get_onDisconnected();
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 () {
client.set_onDisconnected(sinon.spy());
client.set_onUpdateState(sinon.spy());
client._rfb_connection_state = 'other state';
client._updateConnectionState('disconnected');
var updateStateSpy = client.get_onUpdateState();
var disconnectSpy = client.get_onDisconnected();
expect(updateStateSpy.calledBefore(disconnectSpy)).to.be.true;
});
});
@ -594,8 +631,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail on an invalid version', function () {
sinon.spy(client, "_fail");
send_ver('002.000', client);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
});
@ -665,10 +703,11 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail if there are no supported schemes for versions >= 3.7', function () {
sinon.spy(client, "_fail");
client._rfb_version = 3.7;
var auth_schemes = [1, 32];
client._sock._websocket._receive_data(auth_schemes);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () {
@ -717,7 +756,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
sinon.spy(client, '_fail');
client._sock._websocket._receive_data(new Uint8Array(data));
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledWith('Auth failure: Whoopsies');
});
@ -734,9 +772,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail on an unknown auth scheme', function () {
sinon.spy(client, "_fail");
client._rfb_version = 3.8;
send_security(57, client);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
describe('VNC Authentication (type 2) Handler', function () {
@ -876,8 +915,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail if no supported tunnels are listed', function () {
sinon.spy(client, "_fail");
send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
it('should choose the notunnel tunnel type', function () {
@ -919,9 +959,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail if there are no supported auth types', function () {
sinon.spy(client, "_fail");
client._rfb_tightvnc = true;
send_num_str_pairs([[23, 'stdv', 'badval__']], client);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
});
});
@ -947,14 +988,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
sinon.spy(client, '_fail');
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._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledWith('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._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledWith('Authentication failure');
});
});
@ -1316,10 +1357,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail on an unsupported encoding', function () {
client.set_onFBUReceive(sinon.spy());
sinon.spy(client, "_fail");
var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };
send_fbu_msg([rect_info], [[]], client);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
it('should be able to pause and resume receiving rects if not enought data', function () {
@ -1563,10 +1604,11 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail on an invalid subencoding', function () {
sinon.spy(client,"_fail");
var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }];
var rects = [[45]]; // an invalid subencoding
send_fbu_msg(info, rects, client);
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
});
@ -1766,8 +1808,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail on unknown XVP message types', function () {
sinon.spy(client, "_fail");
client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237]));
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
});
@ -1859,8 +1902,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail on an unknown message type', function () {
sinon.spy(client, "_fail");
client._sock._websocket._receive_data(new Uint8Array([87]));
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
});
@ -2055,10 +2099,11 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fail if we are not currently ready to connect and we get an "open" event', function () {
sinon.spy(client, "_fail");
client.connect('host', 8675);
client._rfb_connection_state = 'some_other_state';
client._sock._websocket._open();
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
// close events
@ -2070,10 +2115,11 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should transition to failed if we get a close event from any non-"disconnection" state', function () {
sinon.spy(client, "_fail");
client.connect('host', 8675);
client._rfb_connection_state = 'connected';
client._sock._websocket.close();
expect(client._rfb_connection_state).to.equal('failed');
expect(client._fail).to.have.been.calledOnce;
});
it('should unregister close event handler', function () {

View File

@ -86,6 +86,7 @@
var rfb;
var resizeTimeout;
var desktopName;
function UIresize() {
@ -102,6 +103,9 @@
UIresize();
rfb.set_onFBUComplete(function() { });
}
function updateDesktopName(rfb, name) {
desktopName = name;
}
function passwordRequired(rfb, msg) {
if (typeof msg === 'undefined') {
msg = 'Password Required: ';
@ -112,8 +116,7 @@
html += msg;
html += '<input type=password size=10 id="password_input" class="noVNC_status">';
html += '<\/form>';
document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
document.getElementById('noVNC_status').innerHTML = html;
status(html, "warn");
}
function setPassword() {
rfb.sendPassword(document.getElementById('password_input').value);
@ -135,18 +138,37 @@
rfb.xvpReset();
return false;
}
function updateState(rfb, state, oldstate, msg) {
var s, sb, cad, level;
s = document.getElementById('noVNC_status');
sb = document.getElementById('noVNC_status_bar');
cad = document.getElementById('sendCtrlAltDelButton');
function status(text, level) {
switch (level) {
case 'normal':
case 'warn':
case 'error':
break;
default:
level = "warn";
}
document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_" + level);
document.getElementById('noVNC_status').innerHTML = text;
}
function updateState(rfb, state, oldstate) {
var cad = document.getElementById('sendCtrlAltDelButton');
var encrypt = (rfb && rfb.get_encrypt()) ? 'encrypted' : 'unencrypted';
switch (state) {
case 'failed': level = "error"; break;
case 'fatal': level = "error"; break;
case 'connected': level = "normal"; break;
case 'disconnected': level = "normal"; break;
case 'loaded': level = "normal"; break;
default: level = "warn"; break;
case 'connecting':
status("Connecting", "normal");
break;
case 'connected':
status("Connected (" + encrypt + ") to: " + desktopName, "normal");
break;
case 'disconnecting':
status("Disconnecting", "normal");
break;
case 'disconnected':
status("Disconnected", "normal");
break;
default:
status(state, "warn");
break;
}
if (state === 'connected') {
@ -156,13 +178,14 @@
xvpInit(0);
}
if (typeof(msg) !== 'undefined') {
sb.setAttribute("class", "noVNC_status_" + level);
s.innerHTML = msg;
}
function disconnected(rfb, reason) {
if (typeof(reason) !== 'undefined') {
status(reason, "error");
}
}
function notification(rfb, msg, level, options) {
$D('noVNC_status').innerHTML = msg;
status(msg, level);
}
window.onresize = function () {
@ -226,7 +249,7 @@
}
if ((!host) || (!port)) {
updateState(null, 'fatal', null, 'Must specify host and port in URL');
status('Must specify host and port in URL', 'error');
return;
}
@ -239,13 +262,15 @@
'local_cursor': WebUtil.getConfigVar('cursor', true),
'shared': WebUtil.getConfigVar('shared', true),
'view_only': WebUtil.getConfigVar('view_only', false),
'onNotification: notification,
'onNotification': notification,
'onUpdateState': updateState,
'onDisconnected': disconnected,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired,
'onFBUComplete': FBUComplete});
'onFBUComplete': FBUComplete,
'onDesktopName': updateDesktopName});
} catch (exc) {
updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
status('Unable to create RFB client -- ' + exc, 'error');
return; // don't continue trying to connect
}