From 1eff49d20123fdeb26bf62a9919022e7271135ff Mon Sep 17 00:00:00 2001 From: Ryan Castner Date: Fri, 31 Jan 2020 10:30:14 -0500 Subject: [PATCH] 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. --- core/websock.js | 105 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/core/websock.js b/core/websock.js index 8fef0b28..025a572c 100644 --- a/core/websock.js +++ b/core/websock.js @@ -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 = () => {}; } }