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);
|
process(document.body, true);
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export var l10n = new Localizer();
|
export var l10n = new Localizer();
|
||||||
export default l10n.get.bind(l10n);
|
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 */
|
/* global window, document.getElementById, Util, WebUtil, RFB, Display */
|
||||||
|
|
||||||
import * as Log from '../core/util/logging.js';
|
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 { isTouchDevice } from '../core/util/browsers.js';
|
||||||
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
import { setCapture, getPointerEvent } from '../core/util/events.js';
|
||||||
import KeyTable from "../core/input/keysym.js";
|
import KeyTable from "../core/input/keysym.js";
|
||||||
|
@ -105,7 +105,7 @@ var UI = {
|
||||||
|
|
||||||
UI.updateViewClip();
|
UI.updateViewClip();
|
||||||
|
|
||||||
UI.updateVisualState();
|
UI.updateVisualState('init');
|
||||||
|
|
||||||
document.documentElement.classList.remove("noVNC_loading");
|
document.documentElement.classList.remove("noVNC_loading");
|
||||||
|
|
||||||
|
@ -388,54 +388,41 @@ var UI = {
|
||||||
* VISUAL
|
* VISUAL
|
||||||
* ------v------*/
|
* ------v------*/
|
||||||
|
|
||||||
updateState: function(event) {
|
// Disable/enable controls depending on connection state
|
||||||
var msg;
|
updateVisualState: function(state) {
|
||||||
|
|
||||||
document.documentElement.classList.remove("noVNC_connecting");
|
document.documentElement.classList.remove("noVNC_connecting");
|
||||||
document.documentElement.classList.remove("noVNC_connected");
|
document.documentElement.classList.remove("noVNC_connected");
|
||||||
document.documentElement.classList.remove("noVNC_disconnecting");
|
document.documentElement.classList.remove("noVNC_disconnecting");
|
||||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
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':
|
case 'connecting':
|
||||||
document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
|
transition_elem.textContent = _("Connecting...");
|
||||||
document.documentElement.classList.add("noVNC_connecting");
|
document.documentElement.classList.add("noVNC_connecting");
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
UI.connected = true;
|
|
||||||
UI.inhibit_reconnect = false;
|
|
||||||
UI.doneInitialResize = false;
|
|
||||||
document.documentElement.classList.add("noVNC_connected");
|
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;
|
break;
|
||||||
case 'disconnecting':
|
case 'disconnecting':
|
||||||
UI.connected = false;
|
transition_elem.textContent = _("Disconnecting...");
|
||||||
document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
|
|
||||||
document.documentElement.classList.add("noVNC_disconnecting");
|
document.documentElement.classList.add("noVNC_disconnecting");
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
UI.showStatus(_("Disconnected"));
|
break;
|
||||||
|
case 'reconnecting':
|
||||||
|
transition_elem.textContent = _("Reconnecting...");
|
||||||
|
document.documentElement.classList.add("noVNC_reconnecting");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
msg = "Invalid UI state";
|
Log.Error("Invalid visual state: " + state);
|
||||||
Log.Error(msg);
|
UI.showStatus(_("Internal error"), 'error');
|
||||||
UI.showStatus(msg, 'error');
|
return;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.updateVisualState();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Disable/enable controls depending on connection state
|
|
||||||
updateVisualState: function() {
|
|
||||||
//Log.Debug(">> updateVisualState");
|
|
||||||
|
|
||||||
UI.enableDisableViewClip();
|
UI.enableDisableViewClip();
|
||||||
|
|
||||||
if (UI.connected) {
|
if (UI.connected) {
|
||||||
|
@ -481,8 +468,6 @@ var UI = {
|
||||||
// State change also closes the password dialog
|
// State change also closes the password dialog
|
||||||
document.getElementById('noVNC_password_dlg')
|
document.getElementById('noVNC_password_dlg')
|
||||||
.classList.remove('noVNC_open');
|
.classList.remove('noVNC_open');
|
||||||
|
|
||||||
//Log.Debug("<< updateVisualState");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showStatus: function(text, status_type, time) {
|
showStatus: function(text, status_type, time) {
|
||||||
|
@ -494,21 +479,40 @@ var UI = {
|
||||||
status_type = 'normal';
|
status_type = 'normal';
|
||||||
}
|
}
|
||||||
|
|
||||||
statusElem.classList.remove("noVNC_status_normal");
|
// Don't overwrite more severe visible statuses and never
|
||||||
statusElem.classList.remove("noVNC_status_warn");
|
// errors. Only shows the first error.
|
||||||
statusElem.classList.remove("noVNC_status_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) {
|
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 'warning':
|
||||||
case 'warn':
|
case 'warn':
|
||||||
|
statusElem.classList.remove("noVNC_status_error");
|
||||||
|
statusElem.classList.remove("noVNC_status_normal");
|
||||||
statusElem.classList.add("noVNC_status_warn");
|
statusElem.classList.add("noVNC_status_warn");
|
||||||
break;
|
break;
|
||||||
case 'error':
|
|
||||||
statusElem.classList.add("noVNC_status_error");
|
|
||||||
break;
|
|
||||||
case 'normal':
|
case 'normal':
|
||||||
case 'info':
|
case 'info':
|
||||||
default:
|
default:
|
||||||
|
statusElem.classList.remove("noVNC_status_error");
|
||||||
|
statusElem.classList.remove("noVNC_status_warn");
|
||||||
statusElem.classList.add("noVNC_status_normal");
|
statusElem.classList.add("noVNC_status_normal");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -532,10 +536,6 @@ var UI = {
|
||||||
document.getElementById('noVNC_status').classList.remove("noVNC_open");
|
document.getElementById('noVNC_status').classList.remove("noVNC_open");
|
||||||
},
|
},
|
||||||
|
|
||||||
notification: function (e) {
|
|
||||||
UI.showStatus(e.detail.message, e.detail.level);
|
|
||||||
},
|
|
||||||
|
|
||||||
activateControlbar: function(event) {
|
activateControlbar: function(event) {
|
||||||
clearTimeout(UI.idleControlbarTimeout);
|
clearTimeout(UI.idleControlbarTimeout);
|
||||||
// We manipulate the anchor instead of the actual control
|
// We manipulate the anchor instead of the actual control
|
||||||
|
@ -1012,16 +1012,19 @@ var UI = {
|
||||||
password = undefined;
|
password = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UI.hideStatus();
|
||||||
|
|
||||||
if (!host) {
|
if (!host) {
|
||||||
var msg = _("Must set host");
|
Log.Error("Can't connect when host is: " + host);
|
||||||
Log.Error(msg);
|
UI.showStatus(_("Must set host"), 'error');
|
||||||
UI.showStatus(msg, 'error');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.closeAllPanels();
|
UI.closeAllPanels();
|
||||||
UI.closeConnectPanel();
|
UI.closeConnectPanel();
|
||||||
|
|
||||||
|
UI.updateVisualState('connecting');
|
||||||
|
|
||||||
UI.updateViewOnly();
|
UI.updateViewOnly();
|
||||||
|
|
||||||
var url;
|
var url;
|
||||||
|
@ -1038,10 +1041,10 @@ var UI = {
|
||||||
{ shared: UI.getSetting('shared'),
|
{ shared: UI.getSetting('shared'),
|
||||||
repeaterID: UI.getSetting('repeaterID'),
|
repeaterID: UI.getSetting('repeaterID'),
|
||||||
credentials: { password: password } });
|
credentials: { password: password } });
|
||||||
UI.rfb.addEventListener("notification", UI.notification);
|
UI.rfb.addEventListener("connect", UI.connectFinished);
|
||||||
UI.rfb.addEventListener("updatestate", UI.updateState);
|
|
||||||
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
|
||||||
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
UI.rfb.addEventListener("credentialsrequired", UI.credentials);
|
||||||
|
UI.rfb.addEventListener("securityfailure", UI.securityFailed);
|
||||||
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
|
UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
|
||||||
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
|
||||||
UI.rfb.addEventListener("bell", UI.bell);
|
UI.rfb.addEventListener("bell", UI.bell);
|
||||||
|
@ -1053,9 +1056,13 @@ var UI = {
|
||||||
UI.closeAllPanels();
|
UI.closeAllPanels();
|
||||||
UI.rfb.disconnect();
|
UI.rfb.disconnect();
|
||||||
|
|
||||||
|
UI.connected = false;
|
||||||
|
|
||||||
// Disable automatic reconnecting
|
// Disable automatic reconnecting
|
||||||
UI.inhibit_reconnect = true;
|
UI.inhibit_reconnect = true;
|
||||||
|
|
||||||
|
UI.updateVisualState('disconnecting');
|
||||||
|
|
||||||
// Don't display the connection settings until we're actually disconnected
|
// Don't display the connection settings until we're actually disconnected
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1070,33 +1077,76 @@ var UI = {
|
||||||
UI.connect(null, UI.reconnect_password);
|
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() {
|
cancelReconnect: function() {
|
||||||
if (UI.reconnect_callback !== null) {
|
if (UI.reconnect_callback !== null) {
|
||||||
clearTimeout(UI.reconnect_callback);
|
clearTimeout(UI.reconnect_callback);
|
||||||
UI.reconnect_callback = null;
|
UI.reconnect_callback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.documentElement.classList.remove("noVNC_reconnecting");
|
UI.updateVisualState('disconnected');
|
||||||
|
|
||||||
UI.openControlbar();
|
UI.openControlbar();
|
||||||
UI.openConnectPanel();
|
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
|
* /CONNECTION
|
||||||
* ==============
|
* ==============
|
||||||
|
@ -1112,9 +1162,8 @@ var UI = {
|
||||||
document.getElementById('noVNC_password_input').focus();
|
document.getElementById('noVNC_password_input').focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
var msg = _("Password is required");
|
Log.Warn("Server asked for a password");
|
||||||
Log.Warn(msg);
|
UI.showStatus(_("Password is required"), "warning");
|
||||||
UI.showStatus(msg, "warning");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setPassword: function(e) {
|
setPassword: function(e) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ export var encodings = {
|
||||||
pseudoEncodingContinuousUpdates: -313,
|
pseudoEncodingContinuousUpdates: -313,
|
||||||
pseudoEncodingCompressLevel9: -247,
|
pseudoEncodingCompressLevel9: -247,
|
||||||
pseudoEncodingCompressLevel0: -256,
|
pseudoEncodingCompressLevel0: -256,
|
||||||
}
|
};
|
||||||
|
|
||||||
export function encodingName(num) {
|
export function encodingName(num) {
|
||||||
switch (num) {
|
switch (num) {
|
||||||
|
|
271
core/rfb.js
271
core/rfb.js
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* noVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2012 Joel Martin
|
* 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)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -11,7 +11,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Log from './util/logging.js';
|
import * as Log from './util/logging.js';
|
||||||
import _ from './util/localization.js';
|
|
||||||
import { decodeUTF8 } from './util/strings.js';
|
import { decodeUTF8 } from './util/strings.js';
|
||||||
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
|
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
|
||||||
import EventTargetMixin from './util/eventtarget.js';
|
import EventTargetMixin from './util/eventtarget.js';
|
||||||
|
@ -45,7 +44,7 @@ export default function RFB(target, url, options) {
|
||||||
this._url = url;
|
this._url = url;
|
||||||
|
|
||||||
// Connection details
|
// Connection details
|
||||||
options = options || {}
|
options = options || {};
|
||||||
this._rfb_credentials = options.credentials || {};
|
this._rfb_credentials = options.credentials || {};
|
||||||
this._shared = 'shared' in options ? !!options.shared : true;
|
this._shared = 'shared' in options ? !!options.shared : true;
|
||||||
this._repeaterID = options.repeaterID || '';
|
this._repeaterID = options.repeaterID || '';
|
||||||
|
@ -54,7 +53,7 @@ export default function RFB(target, url, options) {
|
||||||
this._rfb_connection_state = '';
|
this._rfb_connection_state = '';
|
||||||
this._rfb_init_state = '';
|
this._rfb_init_state = '';
|
||||||
this._rfb_auth_scheme = '';
|
this._rfb_auth_scheme = '';
|
||||||
this._rfb_disconnect_reason = "";
|
this._rfb_clean_disconnect = true;
|
||||||
|
|
||||||
// Server capabilities
|
// Server capabilities
|
||||||
this._rfb_version = 0;
|
this._rfb_version = 0;
|
||||||
|
@ -190,38 +189,40 @@ export default function RFB(target, url, options) {
|
||||||
this._rfb_init_state = 'ProtocolVersion';
|
this._rfb_init_state = 'ProtocolVersion';
|
||||||
Log.Debug("Starting VNC handshake");
|
Log.Debug("Starting VNC handshake");
|
||||||
} else {
|
} else {
|
||||||
this._fail("Unexpected server connection");
|
this._fail("Unexpected server connection while " +
|
||||||
|
this._rfb_connection_state);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
this._sock.on('close', function (e) {
|
this._sock.on('close', function (e) {
|
||||||
Log.Warn("WebSocket on-close event");
|
Log.Warn("WebSocket on-close event");
|
||||||
var msg = "";
|
var msg = "";
|
||||||
if (e.code) {
|
if (e.code) {
|
||||||
msg = " (code: " + e.code;
|
msg = "(code: " + e.code;
|
||||||
if (e.reason) {
|
if (e.reason) {
|
||||||
msg += ", reason: " + e.reason;
|
msg += ", reason: " + e.reason;
|
||||||
}
|
}
|
||||||
msg += ")";
|
msg += ")";
|
||||||
}
|
}
|
||||||
switch (this._rfb_connection_state) {
|
switch (this._rfb_connection_state) {
|
||||||
case 'disconnecting':
|
|
||||||
this._updateConnectionState('disconnected');
|
|
||||||
break;
|
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
this._fail('Failed to connect to server', msg);
|
this._fail("Connection closed " + msg);
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
// Handle disconnects that were initiated server-side
|
// Handle disconnects that were initiated server-side
|
||||||
this._updateConnectionState('disconnecting');
|
this._updateConnectionState('disconnecting');
|
||||||
this._updateConnectionState('disconnected');
|
this._updateConnectionState('disconnected');
|
||||||
break;
|
break;
|
||||||
|
case 'disconnecting':
|
||||||
|
// Normal disconnection path
|
||||||
|
this._updateConnectionState('disconnected');
|
||||||
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
this._fail("Unexpected server disconnect",
|
this._fail("Unexpected server disconnect " +
|
||||||
"Already disconnected: " + msg);
|
"when already disconnected " + msg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server disconnect",
|
this._fail("Unexpected server disconnect before connecting " +
|
||||||
"Not in any state yet: " + msg);
|
msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this._sock.off('close');
|
this._sock.off('close');
|
||||||
|
@ -384,9 +385,9 @@ RFB.prototype = {
|
||||||
this._sock.open(this._url, ['binary']);
|
this._sock.open(this._url, ['binary']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name === 'SyntaxError') {
|
if (e.name === 'SyntaxError') {
|
||||||
this._fail("Invalid host or port value given", e);
|
this._fail("Invalid host or port (" + e + ")");
|
||||||
} else {
|
} else {
|
||||||
this._fail("Error while connecting", e);
|
this._fail("Error when opening socket (" + e + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,8 +513,6 @@ RFB.prototype = {
|
||||||
// State change actions
|
// State change actions
|
||||||
|
|
||||||
this._rfb_connection_state = state;
|
this._rfb_connection_state = state;
|
||||||
var event = new CustomEvent("updatestate", { detail: { state: state } });
|
|
||||||
this.dispatchEvent(event);
|
|
||||||
|
|
||||||
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
|
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
|
||||||
Log.Debug(smsg);
|
Log.Debug(smsg);
|
||||||
|
@ -528,59 +527,54 @@ RFB.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (state) {
|
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':
|
case 'connecting':
|
||||||
this._connect();
|
this._connect();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'connected':
|
||||||
|
var event = new CustomEvent("connect", { detail: {} });
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'disconnecting':
|
case 'disconnecting':
|
||||||
this._disconnect();
|
this._disconnect();
|
||||||
|
|
||||||
this._disconnTimer = setTimeout(function () {
|
this._disconnTimer = setTimeout(function () {
|
||||||
this._rfb_disconnect_reason = _("Disconnect timeout");
|
Log.Error("Disconnection timed out.");
|
||||||
this._updateConnectionState('disconnected');
|
this._updateConnectionState('disconnected');
|
||||||
}.bind(this), DISCONNECT_TIMEOUT * 1000);
|
}.bind(this), DISCONNECT_TIMEOUT * 1000);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'disconnected':
|
||||||
|
event = new CustomEvent(
|
||||||
|
"disconnect", { detail:
|
||||||
|
{ clean: this._rfb_clean_disconnect } });
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Print errors and disconnect
|
/* 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.
|
* should be logged but not sent to the user interface.
|
||||||
*/
|
*/
|
||||||
_fail: function (msg, details) {
|
_fail: function (details) {
|
||||||
var fullmsg = msg;
|
|
||||||
if (typeof details !== 'undefined') {
|
|
||||||
fullmsg = msg + " (" + details + ")";
|
|
||||||
}
|
|
||||||
switch (this._rfb_connection_state) {
|
switch (this._rfb_connection_state) {
|
||||||
case 'disconnecting':
|
case 'disconnecting':
|
||||||
Log.Error("Failed when disconnecting: " + fullmsg);
|
Log.Error("Failed when disconnecting: " + details);
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
Log.Error("Failed while connected: " + fullmsg);
|
Log.Error("Failed while connected: " + details);
|
||||||
break;
|
break;
|
||||||
case 'connecting':
|
case 'connecting':
|
||||||
Log.Error("Failed when connecting: " + fullmsg);
|
Log.Error("Failed when connecting: " + details);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.Error("RFB failure: " + fullmsg);
|
Log.Error("RFB failure: " + details);
|
||||||
break;
|
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
|
// Transition to disconnected without waiting for socket to close
|
||||||
this._updateConnectionState('disconnecting');
|
this._updateConnectionState('disconnecting');
|
||||||
|
@ -589,30 +583,6 @@ RFB.prototype = {
|
||||||
return false;
|
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) {
|
_setCapability: function (cap, val) {
|
||||||
this._capabilities[cap] = val;
|
this._capabilities[cap] = val;
|
||||||
var event = new CustomEvent("capabilities",
|
var event = new CustomEvent("capabilities",
|
||||||
|
@ -716,8 +686,7 @@ RFB.prototype = {
|
||||||
|
|
||||||
_negotiate_protocol_version: function () {
|
_negotiate_protocol_version: function () {
|
||||||
if (this._sock.rQlen() < 12) {
|
if (this._sock.rQlen() < 12) {
|
||||||
return this._fail("Error while negotiating with server",
|
return this._fail("Received incomplete protocol version.");
|
||||||
"Incomplete protocol version");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
|
var sversion = this._sock.rQshiftStr(12).substr(4, 7);
|
||||||
|
@ -742,8 +711,7 @@ RFB.prototype = {
|
||||||
this._rfb_version = 3.8;
|
this._rfb_version = 3.8;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Invalid server version " + sversion);
|
||||||
"Invalid server version: " + sversion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_repeater) {
|
if (is_repeater) {
|
||||||
|
@ -785,10 +753,7 @@ RFB.prototype = {
|
||||||
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
|
if (this._sock.rQwait("security type", num_types, 1)) { return false; }
|
||||||
|
|
||||||
if (num_types === 0) {
|
if (num_types === 0) {
|
||||||
var strlen = this._sock.rQshift32();
|
return this._handle_security_failure("no security types");
|
||||||
var reason = this._sock.rQshiftStr(strlen);
|
|
||||||
return this._fail("Error while negotiating with server",
|
|
||||||
"Security failure: " + reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var types = this._sock.rQshiftBytes(num_types);
|
var types = this._sock.rQshiftBytes(num_types);
|
||||||
|
@ -805,8 +770,7 @@ RFB.prototype = {
|
||||||
} else if (includes(2, types)) {
|
} else if (includes(2, types)) {
|
||||||
this._rfb_auth_scheme = 2; // VNC Auth
|
this._rfb_auth_scheme = 2; // VNC Auth
|
||||||
} else {
|
} else {
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Unsupported security types (types: " + types + ")");
|
||||||
"Unsupported security types: " + types);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sock.send([this._rfb_auth_scheme]);
|
this._sock.send([this._rfb_auth_scheme]);
|
||||||
|
@ -822,6 +786,59 @@ RFB.prototype = {
|
||||||
return this._init_msg(); // jump to authentication
|
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
|
// authentication
|
||||||
_negotiate_xvp_auth: function () {
|
_negotiate_xvp_auth: function () {
|
||||||
if (!this._rfb_credentials.username ||
|
if (!this._rfb_credentials.username ||
|
||||||
|
@ -877,15 +894,13 @@ RFB.prototype = {
|
||||||
if (serverSupportedTunnelTypes[0]) {
|
if (serverSupportedTunnelTypes[0]) {
|
||||||
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
|
if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
|
||||||
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
|
serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Client's tunnel type had the incorrect " +
|
||||||
"Client's tunnel type had the incorrect " +
|
|
||||||
"vendor or signature");
|
"vendor or signature");
|
||||||
}
|
}
|
||||||
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
|
||||||
return false; // wait until we receive the sub auth count to continue
|
return false; // wait until we receive the sub auth count to continue
|
||||||
} else {
|
} else {
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Server wanted tunnels, but doesn't support " +
|
||||||
"Server wanted tunnels, but doesn't support " +
|
|
||||||
"the notunnel type");
|
"the notunnel type");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -939,24 +954,19 @@ RFB.prototype = {
|
||||||
this._rfb_auth_scheme = 2;
|
this._rfb_auth_scheme = 2;
|
||||||
return this._init_msg();
|
return this._init_msg();
|
||||||
default:
|
default:
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Unsupported tiny auth scheme " +
|
||||||
"Unsupported tiny auth scheme: " +
|
"(scheme: " + authType + ")");
|
||||||
authType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._fail("Unsupported server",
|
return this._fail("No supported sub-auth types!");
|
||||||
"No supported sub-auth types!");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_negotiate_authentication: function () {
|
_negotiate_authentication: function () {
|
||||||
switch (this._rfb_auth_scheme) {
|
switch (this._rfb_auth_scheme) {
|
||||||
case 0: // connection failed
|
case 0: // connection failed
|
||||||
if (this._sock.rQwait("auth reason", 4)) { return false; }
|
return this._handle_security_failure("authentication scheme");
|
||||||
var strlen = this._sock.rQshift32();
|
|
||||||
var reason = this._sock.rQshiftStr(strlen);
|
|
||||||
return this._fail("Authentication failure", reason);
|
|
||||||
|
|
||||||
case 1: // no auth
|
case 1: // no auth
|
||||||
if (this._rfb_version >= 3.8) {
|
if (this._rfb_version >= 3.8) {
|
||||||
|
@ -976,33 +986,30 @@ RFB.prototype = {
|
||||||
return this._negotiate_tight_auth();
|
return this._negotiate_tight_auth();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this._fail("Unsupported server",
|
return this._fail("Unsupported auth scheme (scheme: " +
|
||||||
"Unsupported auth scheme: " +
|
this._rfb_auth_scheme + ")");
|
||||||
this._rfb_auth_scheme);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handle_security_result: function () {
|
_handle_security_result: function () {
|
||||||
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
|
||||||
switch (this._sock.rQshift32()) {
|
|
||||||
case 0: // OK
|
let status = this._sock.rQshift32();
|
||||||
this._rfb_init_state = 'ClientInitialisation';
|
|
||||||
Log.Debug('Authentication OK');
|
if (status === 0) { // OK
|
||||||
return this._init_msg();
|
this._rfb_init_state = 'ClientInitialisation';
|
||||||
case 1: // failed
|
Log.Debug('Authentication OK');
|
||||||
if (this._rfb_version >= 3.8) {
|
return this._init_msg();
|
||||||
var length = this._sock.rQshift32();
|
} else {
|
||||||
if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
|
if (this._rfb_version >= 3.8) {
|
||||||
var reason = this._sock.rQshiftStr(length);
|
return this._handle_security_failure("security result", status);
|
||||||
return this._fail("Authentication failure", reason);
|
} else {
|
||||||
} else {
|
let event = new CustomEvent("securityfailure",
|
||||||
return this._fail("Authentication failure");
|
{ detail: { status: status } });
|
||||||
}
|
this.dispatchEvent(event);
|
||||||
case 2:
|
|
||||||
return this._fail("Too many authentication attempts");
|
return this._fail("Security handshake failed");
|
||||||
default:
|
}
|
||||||
return this._fail("Unsupported server",
|
|
||||||
"Unknown SecurityResult");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1181,15 +1188,15 @@ RFB.prototype = {
|
||||||
return this._negotiate_server_init();
|
return this._negotiate_server_init();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this._fail("Internal error", "Unknown init state: " +
|
return this._fail("Unknown init state (state: " +
|
||||||
this._rfb_init_state);
|
this._rfb_init_state + ")");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handle_set_colour_map_msg: function () {
|
_handle_set_colour_map_msg: function () {
|
||||||
Log.Debug("SetColorMapEntries");
|
Log.Debug("SetColorMapEntries");
|
||||||
|
|
||||||
return this._fail("Protocol error", "Unexpected SetColorMapEntries message");
|
return this._fail("Unexpected SetColorMapEntries message");
|
||||||
},
|
},
|
||||||
|
|
||||||
_handle_server_cut_text: function () {
|
_handle_server_cut_text: function () {
|
||||||
|
@ -1238,8 +1245,7 @@ RFB.prototype = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!(flags & (1<<31))) {
|
if (!(flags & (1<<31))) {
|
||||||
return this._fail("Internal error",
|
return this._fail("Unexpected fence response");
|
||||||
"Unexpected fence response");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out unsupported flags
|
// Filter out unsupported flags
|
||||||
|
@ -1262,8 +1268,7 @@ RFB.prototype = {
|
||||||
|
|
||||||
switch (xvp_msg) {
|
switch (xvp_msg) {
|
||||||
case 0: // XVP_FAIL
|
case 0: // XVP_FAIL
|
||||||
Log.Error("Operation Failed");
|
Log.Error("XVP Operation Failed");
|
||||||
this._notification("XVP Operation Failed", 'error');
|
|
||||||
break;
|
break;
|
||||||
case 1: // XVP_INIT
|
case 1: // XVP_INIT
|
||||||
this._rfb_xvp_ver = xvp_ver;
|
this._rfb_xvp_ver = xvp_ver;
|
||||||
|
@ -1271,8 +1276,7 @@ RFB.prototype = {
|
||||||
this._setCapability("power", true);
|
this._setCapability("power", true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server message",
|
this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
|
||||||
"Illegal server XVP message " + xvp_msg);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1330,7 +1334,7 @@ RFB.prototype = {
|
||||||
return this._handle_xvp_msg();
|
return this._handle_xvp_msg();
|
||||||
|
|
||||||
default:
|
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));
|
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1385,9 +1389,8 @@ RFB.prototype = {
|
||||||
(hdr[10] << 8) + hdr[11], 10);
|
(hdr[10] << 8) + hdr[11], 10);
|
||||||
|
|
||||||
if (!this._encHandlers[this._FBU.encoding]) {
|
if (!this._encHandlers[this._FBU.encoding]) {
|
||||||
this._fail("Unexpected server message",
|
this._fail("Unsupported encoding (encoding: " +
|
||||||
"Unsupported encoding " +
|
this._FBU.encoding + ")");
|
||||||
this._FBU.encoding);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1881,8 +1884,8 @@ RFB.encodingHandlers = {
|
||||||
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
|
if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
|
||||||
var subencoding = rQ[rQi]; // Peek
|
var subencoding = rQ[rQi]; // Peek
|
||||||
if (subencoding > 30) { // Raw
|
if (subencoding > 30) { // Raw
|
||||||
this._fail("Unexpected server message",
|
this._fail("Illegal hextile subencoding (subencoding: " +
|
||||||
"Illegal hextile subencoding: " + subencoding);
|
subencoding + ")");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2211,9 +2214,8 @@ RFB.encodingHandlers = {
|
||||||
else if (ctl === 0x0A) cmode = "png";
|
else if (ctl === 0x0A) cmode = "png";
|
||||||
else if (ctl & 0x04) cmode = "filter";
|
else if (ctl & 0x04) cmode = "filter";
|
||||||
else if (ctl < 0x04) cmode = "copy";
|
else if (ctl < 0x04) cmode = "copy";
|
||||||
else return this._fail("Unexpected server message",
|
else return this._fail("Illegal tight compression received (ctl: " +
|
||||||
"Illegal tight compression received, " +
|
ctl + ")");
|
||||||
"ctl: " + ctl);
|
|
||||||
|
|
||||||
switch (cmode) {
|
switch (cmode) {
|
||||||
// fill use depth because TPIXELs drop the padding byte
|
// fill use depth because TPIXELs drop the padding byte
|
||||||
|
@ -2273,9 +2275,8 @@ RFB.encodingHandlers = {
|
||||||
} else {
|
} else {
|
||||||
// Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
|
// 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
|
// Filter 2, Gradient is valid but not use if jpeg is enabled
|
||||||
this._fail("Unexpected server message",
|
this._fail("Unsupported tight subencoding received " +
|
||||||
"Unsupported tight subencoding received, " +
|
"(filter: " + filterId + ")");
|
||||||
"filter: " + filterId);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "copy":
|
case "copy":
|
||||||
|
@ -2350,8 +2351,8 @@ RFB.encodingHandlers = {
|
||||||
msg = "Unknown reason";
|
msg = "Unknown reason";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this._notification("Server did not accept the resize request: "
|
Log.Warn("Server did not accept the resize request: "
|
||||||
+ msg, 'normal');
|
+ msg);
|
||||||
} else {
|
} else {
|
||||||
this._resize(this._FBU.width, this._FBU.height);
|
this._resize(this._FBU.width, this._FBU.height);
|
||||||
}
|
}
|
||||||
|
|
80
docs/API.md
80
docs/API.md
|
@ -63,13 +63,9 @@ protocol stream.
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
[`updatestate`](#updatestate)
|
[`connect`](#connect)
|
||||||
- The `updatestate` event is fired when the connection state of the
|
- The `connect` event is fired when the `RFB` object has completed
|
||||||
`RFB` object changes.
|
the connection and handshaking with the server.
|
||||||
|
|
||||||
[`notification`](#notification)
|
|
||||||
- The `notification` event is fired when the `RFB` usage has a
|
|
||||||
message to display to the user.
|
|
||||||
|
|
||||||
[`disconnect`](#disconnected)
|
[`disconnect`](#disconnected)
|
||||||
- The `disconnect` event is fired when the `RFB` object disconnects.
|
- 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
|
- The `credentialsrequired` event is fired when more credentials must
|
||||||
be given to continue.
|
be given to continue.
|
||||||
|
|
||||||
|
[`securityfailure`](#securityfailure)
|
||||||
|
- The `securityfailure` event is fired when the security negotiation
|
||||||
|
with the server fails.
|
||||||
|
|
||||||
[`clipboard`](#clipboard)
|
[`clipboard`](#clipboard)
|
||||||
- The `clipboard` event is fired when clipboard data is received from
|
- The `clipboard` event is fired when clipboard data is received from
|
||||||
the server.
|
the server.
|
||||||
|
@ -181,49 +181,19 @@ connection to a specified VNC server.
|
||||||
- A `DOMString` specifying the ID to provide to any VNC repeater
|
- A `DOMString` specifying the ID to provide to any VNC repeater
|
||||||
encountered.
|
encountered.
|
||||||
|
|
||||||
#### updatestate
|
#### connect
|
||||||
|
|
||||||
The `updatestate` event is fired after the noVNC connection state
|
The `connect` event is fired after all the handshaking with the server
|
||||||
changes. The `detail` property is an `Object` containg the property
|
is completed and the connection is fully established. After this event
|
||||||
`state` with the new connection state.
|
the `RFB` object is ready to recieve graphics updates and to send input.
|
||||||
|
|
||||||
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"`
|
|
||||||
|
|
||||||
#### disconnect
|
#### disconnect
|
||||||
|
|
||||||
The `disconnect` event is fired when the connection has been
|
The `disconnect` event is fired when the connection has been
|
||||||
terminated. The `detail` property is an `Object` the optionally
|
terminated. The `detail` property is an `Object` that contains the
|
||||||
contains the property `reason`. `reason` is a `DOMString` specifying
|
property `clean`. `clean` is a `boolean` indicating if the termination
|
||||||
the reason in the event of an unexpected termination. `reason` will be
|
was clean or not. In the event of an unexpected termination or an error
|
||||||
omitted for a clean termination.
|
`clean` will be set to false.
|
||||||
|
|
||||||
#### credentialsrequired
|
#### 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
|
property is an `Object` containing the property `types` which is an
|
||||||
`Array` of `DOMString` listing the credentials that are required.
|
`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
|
#### clipboard
|
||||||
|
|
||||||
The `clipboard` event is fired when the server has sent clipboard data.
|
The `clipboard` event is fired when the server has sent clipboard data.
|
||||||
|
|
|
@ -61,6 +61,7 @@ module.exports = function(config) {
|
||||||
files: [
|
files: [
|
||||||
{ pattern: 'vendor/sinon.js', included: false },
|
{ pattern: 'vendor/sinon.js', included: false },
|
||||||
{ pattern: 'node_modules/sinon-chai/lib/sinon-chai.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: 'core/**/*.js', included: false },
|
||||||
{ pattern: 'vendor/pako/**/*.js', included: false },
|
{ pattern: 'vendor/pako/**/*.js', included: false },
|
||||||
{ pattern: 'tests/test.*.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
|
// preprocess matching files before serving them to the browser
|
||||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
|
'app/localization.js': ['babel'],
|
||||||
'core/**/*.js': ['babel'],
|
'core/**/*.js': ['babel'],
|
||||||
'tests/test.*.js': ['babel'],
|
'tests/test.*.js': ['babel'],
|
||||||
'tests/fake.*.js': ['babel'],
|
'tests/fake.*.js': ['babel'],
|
||||||
|
|
|
@ -52,10 +52,6 @@ function enableUI() {
|
||||||
encoding = VNC_frame_encoding;
|
encoding = VNC_frame_encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
const notification = function (rfb, mesg, level, options) {
|
|
||||||
document.getElementById('VNC_status').textContent = mesg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function IterationPlayer (iterations, frames, encoding) {
|
function IterationPlayer (iterations, frames, encoding) {
|
||||||
this._iterations = iterations;
|
this._iterations = iterations;
|
||||||
|
|
||||||
|
@ -72,7 +68,6 @@ function IterationPlayer (iterations, frames, encoding) {
|
||||||
this.onfinish = function() {};
|
this.onfinish = function() {};
|
||||||
this.oniterationfinish = function() {};
|
this.oniterationfinish = function() {};
|
||||||
this.rfbdisconnected = function() {};
|
this.rfbdisconnected = function() {};
|
||||||
this.rfbnotification = function() {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IterationPlayer.prototype = {
|
IterationPlayer.prototype = {
|
||||||
|
@ -87,7 +82,7 @@ IterationPlayer.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_nextIteration: function () {
|
_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);
|
player.onfinish = this._iterationFinish.bind(this);
|
||||||
|
|
||||||
if (this._state !== 'running') { return; }
|
if (this._state !== 'running') { return; }
|
||||||
|
@ -120,26 +115,17 @@ IterationPlayer.prototype = {
|
||||||
this._nextIteration();
|
this._nextIteration();
|
||||||
},
|
},
|
||||||
|
|
||||||
_disconnected: function (rfb, reason, frame) {
|
_disconnected: function (rfb, clean, frame) {
|
||||||
if (reason) {
|
if (!clean) {
|
||||||
this._state = 'failed';
|
this._state = 'failed';
|
||||||
}
|
}
|
||||||
|
|
||||||
var evt = new Event('rfbdisconnected');
|
var evt = new Event('rfbdisconnected');
|
||||||
evt.reason = reason;
|
evt.clean = clean;
|
||||||
evt.frame = frame;
|
evt.frame = frame;
|
||||||
|
|
||||||
this.onrfbdisconnected(evt);
|
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() {
|
function start() {
|
||||||
|
@ -167,9 +153,6 @@ function start() {
|
||||||
message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`);
|
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) {
|
player.onfinish = function (evt) {
|
||||||
const iterTime = parseInt(evt.duration / evt.iterations, 10);
|
const iterTime = parseInt(evt.duration / evt.iterations, 10);
|
||||||
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
|
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
|
||||||
|
|
|
@ -44,12 +44,11 @@ if (setImmediate === undefined) {
|
||||||
window.addEventListener("message", _onMessage);
|
window.addEventListener("message", _onMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RecordingPlayer (frames, encoding, disconnected, notification) {
|
export default function RecordingPlayer (frames, encoding, disconnected) {
|
||||||
this._frames = frames;
|
this._frames = frames;
|
||||||
this._encoding = encoding;
|
this._encoding = encoding;
|
||||||
|
|
||||||
this._disconnected = disconnected;
|
this._disconnected = disconnected;
|
||||||
this._notification = notification;
|
|
||||||
|
|
||||||
if (this._encoding === undefined) {
|
if (this._encoding === undefined) {
|
||||||
let frame = this._frames[0];
|
let frame = this._frames[0];
|
||||||
|
@ -79,8 +78,8 @@ RecordingPlayer.prototype = {
|
||||||
// initialize a new RFB
|
// initialize a new RFB
|
||||||
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
|
this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test');
|
||||||
this._rfb.viewOnly = true;
|
this._rfb.viewOnly = true;
|
||||||
this._rfb.ondisconnected = this._handleDisconnect.bind(this);
|
this._rfb.addEventListener("disconnect",
|
||||||
this._rfb.onnotification = this._notification;
|
this._handleDisconnect.bind(this));
|
||||||
this._enablePlaybackMode();
|
this._enablePlaybackMode();
|
||||||
|
|
||||||
// reset the frame index and timer
|
// reset the frame index and timer
|
||||||
|
@ -188,8 +187,8 @@ RecordingPlayer.prototype = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleDisconnect(rfb, reason) {
|
_handleDisconnect(rfb, clean) {
|
||||||
this._running = false;
|
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 () {
|
describe('#RFB', function () {
|
||||||
it('should set the current state to "connecting"', function () {
|
it('should set the current state to "connecting"', function () {
|
||||||
var client = new RFB(document.createElement('canvas'), 'wss://host:8675');
|
var client = new RFB(document.createElement('canvas'), 'wss://host:8675');
|
||||||
var spy = sinon.spy();
|
client._rfb_connection_state = '';
|
||||||
client.addEventListener("updatestate", spy);
|
|
||||||
this.clock.tick();
|
this.clock.tick();
|
||||||
expect(spy).to.have.been.calledOnce;
|
expect(client._rfb_connection_state).to.equal('connecting');
|
||||||
expect(spy.args[0][0].detail.state).to.equal('connecting');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should actually connect to the websocket', function () {
|
it('should actually connect to the websocket', function () {
|
||||||
|
@ -90,13 +88,15 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
client = make_rfb();
|
client = make_rfb();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the current state to "disconnecting"', function () {
|
it('should go to state "disconnecting" before "disconnected"', function () {
|
||||||
var spy = sinon.spy();
|
sinon.spy(client, '_updateConnectionState');
|
||||||
client.addEventListener("updatestate", spy);
|
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
expect(spy).to.have.been.calledTwice;
|
expect(client._updateConnectionState).to.have.been.calledTwice;
|
||||||
expect(spy.args[0][0].detail.state).to.equal('disconnecting');
|
expect(client._updateConnectionState.getCall(0).args[0])
|
||||||
expect(spy.args[1][0].detail.state).to.equal('disconnected');
|
.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 () {
|
it('should unregister error event handler', function () {
|
||||||
|
@ -320,13 +320,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
expect(client._disconnTimer).to.be.null;
|
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 () {
|
it('should set the rfb_connection_state', function () {
|
||||||
client._rfb_connection_state = 'disconnecting';
|
client._rfb_connection_state = 'disconnecting';
|
||||||
client._updateConnectionState('disconnected');
|
client._updateConnectionState('disconnected');
|
||||||
|
@ -340,16 +333,23 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore state changes to the same state', function () {
|
it('should ignore state changes to the same state', function () {
|
||||||
var spy = sinon.spy();
|
var connectSpy = sinon.spy();
|
||||||
client.addEventListener("updatestate", spy);
|
var disconnectSpy = sinon.spy();
|
||||||
client._rfb_connection_state = 'connecting';
|
client.addEventListener("connect", connectSpy);
|
||||||
client._updateConnectionState('connecting');
|
client.addEventListener("disconnect", disconnectSpy);
|
||||||
expect(spy).to.not.have.been.called;
|
|
||||||
|
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 () {
|
it('should ignore illegal state changes', function () {
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("updatestate", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._rfb_connection_state = 'connected';
|
client._rfb_connection_state = 'connected';
|
||||||
client._updateConnectionState('disconnected');
|
client._updateConnectionState('disconnected');
|
||||||
expect(client._rfb_connection_state).to.not.equal('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');
|
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._rfb_connection_state = 'connected';
|
||||||
client._fail('a reason');
|
client._fail();
|
||||||
expect(client._rfb_disconnect_reason).to.equal('a reason');
|
expect(client._rfb_clean_disconnect).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not include details in disconnect_reason', function () {
|
it('should result in disconnect event with clean set to false', 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 () {
|
|
||||||
client._rfb_connection_state = 'connected';
|
client._rfb_connection_state = 'connected';
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._fail('a reason');
|
client._fail();
|
||||||
this.clock.tick(2000);
|
this.clock.tick(2000);
|
||||||
expect(spy).to.have.been.calledOnce;
|
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('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 () {
|
describe('disconnecting', function () {
|
||||||
var client;
|
var client;
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -459,17 +466,16 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
var client;
|
var client;
|
||||||
beforeEach(function () { client = make_rfb(); });
|
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();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._rfb_connection_state = 'disconnecting';
|
client._rfb_connection_state = 'disconnecting';
|
||||||
client._rfb_disconnect_reason = "error";
|
|
||||||
client._updateConnectionState('disconnected');
|
client._updateConnectionState('disconnected');
|
||||||
expect(spy).to.have.been.calledOnce;
|
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();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._sock._websocket.close = function () {}; // explicitly don't call onclose
|
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;
|
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();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("disconnect", spy);
|
client.addEventListener("disconnect", spy);
|
||||||
client._rfb_connection_state = 'disconnecting';
|
client._rfb_connection_state = 'disconnecting';
|
||||||
|
@ -486,19 +492,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
expect(spy).to.have.been.calledOnce;
|
expect(spy).to.have.been.calledOnce;
|
||||||
expect(spy.args[0].length).to.equal(1);
|
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 () {
|
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.calledOnce;
|
||||||
expect(client._fail).to.have.been.calledWith(
|
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 () {
|
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');
|
sinon.spy(client, '_fail');
|
||||||
client._sock._websocket._receive_data(new Uint8Array(data));
|
client._sock._websocket._receive_data(new Uint8Array(data));
|
||||||
expect(client._fail).to.have.been.calledWith(
|
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 () {
|
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];
|
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));
|
client._sock._websocket._receive_data(new Uint8Array(failure_data));
|
||||||
expect(client._fail).to.have.been.calledWith(
|
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 () {
|
it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
|
||||||
sinon.spy(client, '_fail');
|
sinon.spy(client, '_fail');
|
||||||
client._rfb_version = 3.7;
|
client._rfb_version = 3.7;
|
||||||
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1]));
|
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 () {
|
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 () {
|
it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {
|
||||||
var spy = sinon.spy();
|
var spy = sinon.spy();
|
||||||
client.addEventListener("capabilities", spy);
|
client.addEventListener("capabilities", spy);
|
||||||
|
|
|
@ -4,7 +4,6 @@ var assert = chai.assert;
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
|
|
||||||
import * as Log from '../core/util/logging.js';
|
import * as Log from '../core/util/logging.js';
|
||||||
import l10nGet, { l10n } from '../core/util/localization.js';
|
|
||||||
|
|
||||||
import sinon from '../vendor/sinon.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 the conf_default and conf_defaults methods
|
||||||
// TODO(directxman12): test decodeUTF8
|
// TODO(directxman12): test decodeUTF8
|
||||||
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
|
// 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() {
|
function do_test() {
|
||||||
document.getElementById('startButton').value = "Running";
|
document.getElementById('startButton').value = "Running";
|
||||||
document.getElementById('startButton').disabled = true;
|
document.getElementById('startButton').disabled = true;
|
||||||
|
|
|
@ -147,50 +147,27 @@
|
||||||
document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level;
|
document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level;
|
||||||
document.getElementById('noVNC_status').textContent = text;
|
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') {
|
function connected(e) {
|
||||||
cad.disabled = false;
|
document.getElementById('sendCtrlAltDelButton').disabled = false;
|
||||||
|
doneInitialResize = false;
|
||||||
|
if (WebUtil.getConfigVar('encrypt',
|
||||||
|
(window.location.protocol === "https:"))) {
|
||||||
|
status("Connected (encrypted) to " + desktopName, "normal");
|
||||||
} else {
|
} else {
|
||||||
cad.disabled = true;
|
status("Connected (unencrypted) to " + desktopName, "normal");
|
||||||
updatePowerButtons();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
function disconnected(e) {
|
||||||
function disconnect(e) {
|
document.getElementById('sendCtrlAltDelButton').disabled = true;
|
||||||
if (typeof(e.detail.reason) !== 'undefined') {
|
updatePowerButtons();
|
||||||
status(e.detail.reason, "error");
|
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 () {
|
window.onresize = function () {
|
||||||
// When the window has been resized, wait until the size remains
|
// When the window has been resized, wait until the size remains
|
||||||
|
@ -249,6 +226,8 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
status("Connecting", "normal");
|
||||||
|
|
||||||
if ((!host) || (!port)) {
|
if ((!host) || (!port)) {
|
||||||
status('Must specify host and port in URL', 'error');
|
status('Must specify host and port in URL', 'error');
|
||||||
}
|
}
|
||||||
|
@ -272,10 +251,9 @@
|
||||||
{ repeaterID: WebUtil.getConfigVar('repeaterID', ''),
|
{ repeaterID: WebUtil.getConfigVar('repeaterID', ''),
|
||||||
shared: WebUtil.getConfigVar('shared', true),
|
shared: WebUtil.getConfigVar('shared', true),
|
||||||
credentials: { password: password } });
|
credentials: { password: password } });
|
||||||
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
|
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
|
||||||
rfb.addEventListener("notification", notification);
|
rfb.addEventListener("connect", connected);
|
||||||
rfb.addEventListener("updatestate", updateState);
|
rfb.addEventListener("disconnect", disconnected);
|
||||||
rfb.addEventListener("disconnect", disconnect);
|
|
||||||
rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
|
rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
|
||||||
rfb.addEventListener("credentialsrequired", credentials);
|
rfb.addEventListener("credentialsrequired", credentials);
|
||||||
rfb.addEventListener("desktopname", updateDesktopName);
|
rfb.addEventListener("desktopname", updateDesktopName);
|
||||||
|
|
Loading…
Reference in New Issue