Initiate connection from RFB constructor

We need to do this in order to safely attach to existing WebSocket
objects. There may be message events already pending so we must set up
our event handlers before returning.

This means we will now throw errors instead of generating "disconnect"
events on problems as the caller no longer has the opportunity to set up
event handlers.

This might have been the correct approach from the start as it mimics
how e.g. the WebSocket constructor works.
This commit is contained in:
Pierre Ossman 2021-04-18 11:26:51 +02:00
parent de9fc9508c
commit dbd519558c
2 changed files with 17 additions and 51 deletions

View File

@ -239,9 +239,8 @@ export default class RFB extends EventTargetMixin {
this._sock.on('message', this._handleMessage.bind(this)); this._sock.on('message', this._handleMessage.bind(this));
this._sock.on('error', this._socketError.bind(this)); this._sock.on('error', this._socketError.bind(this));
// Slight delay of the actual connection so that the caller has // All prepared, kick off the connection
// time to set up callbacks this._updateConnectionState('connecting');
setTimeout(this._updateConnectionState.bind(this, 'connecting'));
Log.Debug("<< RFB.constructor"); Log.Debug("<< RFB.constructor");
@ -463,29 +462,20 @@ export default class RFB extends EventTargetMixin {
Log.Debug(">> RFB.connect"); Log.Debug(">> RFB.connect");
if (this._url) { if (this._url) {
try { Log.Info(`connecting to ${this._url}`);
Log.Info(`connecting to ${this._url}`); this._sock.open(this._url, this._wsProtocols);
this._sock.open(this._url, this._wsProtocols);
} catch (e) {
if (e.name === 'SyntaxError') {
this._fail("Invalid host or port (" + e + ")");
} else {
this._fail("Error when opening socket (" + e + ")");
}
}
} else { } else {
try { Log.Info(`attaching ${this._rawChannel} to Websock`);
Log.Info(`attaching ${this._rawChannel} to Websock`); this._sock.attach(this._rawChannel);
this._sock.attach(this._rawChannel);
} catch (e) {
this._fail("Error attaching channel (" + e + ")");
}
if (this._sock.readyState === 'closed') { if (this._sock.readyState === 'closed') {
this._fail("Cannot use already closed WebSocket/RTCDataChannel"); throw Error("Cannot use already closed WebSocket/RTCDataChannel");
} }
if (this._sock.readyState === 'open') { if (this._sock.readyState === 'open') {
// FIXME: _socketOpen() can in theory call _fail(), which
// isn't allowed this early, but I'm not sure that can
// happen without a bug messing up our state variables
this._socketOpen(); this._socketOpen();
} }
} }

View File

@ -151,34 +151,21 @@ describe('Remote Frame Buffer Protocol Client', function () {
attach.restore(); attach.restore();
}); });
it('should not connect from constructor', function () {
new RFB(document.createElement('div'), 'wss://host:8675');
expect(open).to.not.have.been.called;
this.clock.tick(); // Flush the pending connection
});
it('should actually connect to the websocket', function () { it('should actually connect to the websocket', function () {
new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
this.clock.tick();
expect(open).to.have.been.calledOnceWithExactly('ws://HOST:8675/PATH', []); expect(open).to.have.been.calledOnceWithExactly('ws://HOST:8675/PATH', []);
}); });
it('should report connection problems via event', function () { it('should pass on connection problems', function () {
open.restore(); open.restore();
open = sinon.stub(Websock.prototype, 'open'); open = sinon.stub(Websock.prototype, 'open');
open.throws(Error('Failure')); open.throws(new Error('Failure'));
const client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); expect(() => new RFB(document.createElement('div'), 'ws://HOST:8675/PATH')).to.throw('Failure');
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
this.clock.tick();
expect(callback).to.have.been.calledOnce;
expect(callback.args[0][0].detail.clean).to.be.false;
}); });
it('should handle WebSocket/RTCDataChannel objects', function () { it('should handle WebSocket/RTCDataChannel objects', function () {
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []); let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
new RFB(document.createElement('div'), sock); new RFB(document.createElement('div'), sock);
this.clock.tick();
expect(open).to.not.have.been.called; expect(open).to.not.have.been.called;
expect(attach).to.have.been.calledOnceWithExactly(sock); expect(attach).to.have.been.calledOnceWithExactly(sock);
}); });
@ -189,7 +176,6 @@ describe('Remote Frame Buffer Protocol Client', function () {
const client = new RFB(document.createElement('div'), sock); const client = new RFB(document.createElement('div'), sock);
let callback = sinon.spy(); let callback = sinon.spy();
client.addEventListener('disconnect', callback); client.addEventListener('disconnect', callback);
this.clock.tick();
expect(open).to.not.have.been.called; expect(open).to.not.have.been.called;
expect(attach).to.have.been.calledOnceWithExactly(sock); expect(attach).to.have.been.calledOnceWithExactly(sock);
// Check if it is ready for some data // Check if it is ready for some data
@ -200,25 +186,15 @@ describe('Remote Frame Buffer Protocol Client', function () {
it('should refuse closed WebSocket/RTCDataChannel objects', function () { it('should refuse closed WebSocket/RTCDataChannel objects', function () {
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []); let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
sock.readyState = WebSocket.CLOSED; sock.readyState = WebSocket.CLOSED;
const client = new RFB(document.createElement('div'), sock); expect(() => new RFB(document.createElement('div'), sock)).to.throw();
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
this.clock.tick();
expect(callback).to.have.been.calledOnce;
expect(callback.args[0][0].detail.clean).to.be.false;
}); });
it('should report attach problems via event', function () { it('should pass on attach problems', function () {
attach.restore(); attach.restore();
attach = sinon.stub(Websock.prototype, 'attach'); attach = sinon.stub(Websock.prototype, 'attach');
attach.throws(Error('Failure')); attach.throws(new Error('Failure'));
let sock = new FakeWebSocket('ws://HOST:8675/PATH', []); let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);
const client = new RFB(document.createElement('div'), sock); expect(() => new RFB(document.createElement('div'), sock)).to.throw('Failure');
let callback = sinon.spy();
client.addEventListener('disconnect', callback);
this.clock.tick();
expect(callback).to.have.been.calledOnce;
expect(callback.args[0][0].detail.clean).to.be.false;
}); });
}); });