Refactor Websock to WebChannel

The name WebChannel was chosen to represent the opaque datachannel used, either WebSocket or RTCDataChannel. The open method was refactored to take an object of uri and protocols or webChannel and channelType. We introduce a channelStates enum and helper method that provides compatible states across WebSocket and RTCDataChannel. Call sites for logging are refactored to list the channelType (WebSocket or RTCDataChannel) to be more descriptive.
This commit is contained in:
Ryan Castner 2020-01-31 10:30:14 -05:00
parent 71bb3fdfa5
commit 1eff49d201
1 changed files with 75 additions and 30 deletions

View File

@ -1,12 +1,12 @@
/*
* Websock: high-performance binary WebSockets
* WebChannel: high-performance binary WebSocket / RTCDataChannel
* Copyright (C) 2019 The noVNC Authors
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but with extra
* buffer handling.
* WebChannel is similar to the standard WebSocket / RTCDataChannel object
* but with extra buffer handling.
*
* Websock has built-in receive queue buffering; the message event
* WebChannel has built-in receive queue buffering; the message event
* does not contain actual data but is simply a notification that
* there is new data available. Several rQ* methods are available to
* read binary data off of the receive queue.
@ -22,9 +22,11 @@ import * as Log from './util/logging.js';
const ENABLE_COPYWITHIN = false;
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
export default class Websock {
export default class WebChannel {
constructor() {
this._websocket = null; // WebSocket object
this._webChannel = null; // WebSocket or RTCDataChannel object
this._channelType = ""; // Track which type of channel
this._channelStates = null; // Cross compatible states enum for WebSocket / RTCDataChannel
this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue
@ -143,8 +145,8 @@ export default class Websock {
// Send Queue
flush() {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
this._websocket.send(this._encode_message());
if (this._sQlen > 0 && this._webChannel.readyState === this._channelStates.OPEN) {
this._webChannel.send(this._encode_message());
this._sQlen = 0;
}
}
@ -173,49 +175,92 @@ export default class Websock {
this._sQ = new Uint8Array(this._sQbufferSize);
}
_getChannelStates(channelType) {
if (channelType === "WebSocket") {
return {
CONNECTING: WebSocket.CONNECTING,
OPEN: WebSocket.OPEN,
CLOSING: WebSocket.CLOSING,
CLOSED: WebSocket.CLOSED
};
}
if (channelType === "RTCDataChannel") {
// Constants pulled from RTCDataChannelState enum
// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum
return {
CONNECTING: "connecting",
OPEN: "open",
CLOSING: "closing",
CLOSED: "closed"
};
}
throw new Error(`channelType: ${channelType} not recognized`);
}
init() {
this._allocate_buffers();
this._rQi = 0;
this._websocket = null;
this._webChannel = null;
}
open(uri, protocols) {
open({ uri, protocols, webChannel, channelType }) {
this.init();
this._websocket = new WebSocket(uri, protocols);
this._websocket.binaryType = 'arraybuffer';
if (uri) {
this._webChannel = new WebSocket(uri, protocols);
this._channelType = "WebSocket";
this._channelStates = this._getChannelStates("WebSocket");
} else if (webChannel && channelType) {
this._webChannel = webChannel;
this._channelType = channelType;
this._channelStates = this._getChannelStates(channelType);
} else {
throw new Error(
`Expected to receive one of uri and optional protocols or webChannel and channelType`
);
}
this._websocket.onmessage = this._recv_message.bind(this);
this._websocket.onopen = () => {
Log.Debug('>> WebSock.onopen');
if (this._websocket.protocol) {
Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
const onOpen = () => {
Log.Debug(`>> ${this._channelType}.onopen`);
if (this._webChannel.protocol) {
Log.Info("Server choose sub-protocol: " + this._webChannel.protocol);
}
this._eventHandlers.open();
Log.Debug("<< WebSock.onopen");
Log.Debug(`<< ${this._channelType}.onopen`);
};
this._websocket.onclose = (e) => {
Log.Debug(">> WebSock.onclose");
this._webChannel.binaryType = "arraybuffer";
this._webChannel.onmessage = this._recv_message.bind(this);
if (uri) {
this._webChannel.onopen = onOpen;
}
if (webChannel) {
onOpen();
}
this._webChannel.onclose = (e) => {
Log.Debug(`>> ${this._channelType}.onclose`);
this._eventHandlers.close(e);
Log.Debug("<< WebSock.onclose");
Log.Debug(`<< ${this._channelType}.onclose`);
};
this._websocket.onerror = (e) => {
Log.Debug(">> WebSock.onerror: " + e);
this._webChannel.onerror = (e) => {
Log.Debug(`>> ${this._channelType}.onerror: ` + e);
this._eventHandlers.error(e);
Log.Debug("<< WebSock.onerror: " + e);
Log.Debug(`<< ${this._channelType}.onerror: ` + e);
};
}
close() {
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
Log.Info("Closing WebSocket connection");
this._websocket.close();
if (this._webChannel) {
if ((this._webChannel.readyState === this._channelStates.OPEN) ||
(this._webChannel.readyState === this._channelStates.CONNECTING)) {
Log.Info(`Closing ${this._channelType} connection`);
this._webChannel.close();
}
this._websocket.onmessage = () => {};
this._webChannel.onmessage = () => {};
}
}