diff --git a/src/services/y-websocket.js b/src/services/y-websocket.js index ecc78951c..8bfb74d1f 100644 --- a/src/services/y-websocket.js +++ b/src/services/y-websocket.js @@ -3,6 +3,7 @@ */ /* eslint-env browser */ +/* eslint-disable jsdoc/require-param-description */ import * as Y from 'yjs' // eslint-disable-line import * as bc from 'lib0/broadcastchannel' @@ -29,70 +30,70 @@ export const messageAuth = 2 const messageHandlers = [] messageHandlers[messageSync] = ( - encoder, - decoder, - provider, - emitSynced, - _messageType + encoder, + decoder, + provider, + emitSynced, + _messageType, ) => { - encoding.writeVarUint(encoder, messageSync) - const syncMessageType = syncProtocol.readSyncMessage( - decoder, - encoder, - provider.doc, - provider - ) - if ( - emitSynced && syncMessageType === syncProtocol.messageYjsSyncStep2 && - !provider.synced - ) { - provider.synced = true - } + encoding.writeVarUint(encoder, messageSync) + const syncMessageType = syncProtocol.readSyncMessage( + decoder, + encoder, + provider.doc, + provider, + ) + if ( + emitSynced && syncMessageType === syncProtocol.messageYjsSyncStep2 + && !provider.synced + ) { + provider.synced = true + } } messageHandlers[messageQueryAwareness] = ( - encoder, - _decoder, - provider, - _emitSynced, - _messageType + encoder, + _decoder, + provider, + _emitSynced, + _messageType, ) => { - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array( - encoder, - awarenessProtocol.encodeAwarenessUpdate( - provider.awareness, - Array.from(provider.awareness.getStates().keys()) - ) - ) + encoding.writeVarUint(encoder, messageAwareness) + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate( + provider.awareness, + Array.from(provider.awareness.getStates().keys()), + ), + ) } messageHandlers[messageAwareness] = ( - _encoder, - decoder, - provider, - _emitSynced, - _messageType + _encoder, + decoder, + provider, + _emitSynced, + _messageType, ) => { - awarenessProtocol.applyAwarenessUpdate( - provider.awareness, - decoding.readVarUint8Array(decoder), - provider - ) + awarenessProtocol.applyAwarenessUpdate( + provider.awareness, + decoding.readVarUint8Array(decoder), + provider, + ) } messageHandlers[messageAuth] = ( - _encoder, - decoder, - provider, - _emitSynced, - _messageType + _encoder, + decoder, + provider, + _emitSynced, + _messageType, ) => { - authProtocol.readAuthMessage( - decoder, - provider.doc, - (_ydoc, reason) => permissionDeniedHandler(provider, reason) - ) + authProtocol.readAuthMessage( + decoder, + provider.doc, + (_ydoc, reason) => permissionDeniedHandler(provider, reason), + ) } // @todo - this should depend on awareness.outdatedTime @@ -103,7 +104,7 @@ const messageReconnectTimeout = 30000 * @param {string} reason */ const permissionDeniedHandler = (provider, reason) => - console.warn(`Permission denied to access ${provider.url}.\n${reason}`) + console.warn(`Permission denied to access ${provider.url}.\n${reason}`) /** * @param {WebsocketProvider} provider @@ -112,102 +113,102 @@ const permissionDeniedHandler = (provider, reason) => * @return {encoding.Encoder} */ const readMessage = (provider, buf, emitSynced) => { - const decoder = decoding.createDecoder(buf) - const encoder = encoding.createEncoder() - const messageType = decoding.readVarUint(decoder) - const messageHandler = provider.messageHandlers[messageType] - if (/** @type {any} */ (messageHandler)) { - messageHandler(encoder, decoder, provider, emitSynced, messageType) - } else { - console.error('Unable to compute message') - } - return encoder + const decoder = decoding.createDecoder(buf) + const encoder = encoding.createEncoder() + const messageType = decoding.readVarUint(decoder) + const messageHandler = provider.messageHandlers[messageType] + if (/** @type {any} */ (messageHandler)) { + messageHandler(encoder, decoder, provider, emitSynced, messageType) + } else { + console.error('Unable to compute message') + } + return encoder } /** * @param {WebsocketProvider} provider */ const setupWS = (provider) => { - if (provider.shouldConnect && provider.ws === null) { - const websocket = new provider._WS(provider.url, provider.protocols) - websocket.binaryType = 'arraybuffer' - provider.ws = websocket - provider.wsconnecting = true - provider.wsconnected = false - provider.synced = false + if (provider.shouldConnect && provider.ws === null) { + const websocket = new provider._WS(provider.url, provider.protocols) + websocket.binaryType = 'arraybuffer' + provider.ws = websocket + provider.wsconnecting = true + provider.wsconnected = false + provider.synced = false - websocket.onmessage = (event) => { - provider.wsLastMessageReceived = time.getUnixTime() - const encoder = readMessage(provider, new Uint8Array(event.data), true) - if (encoding.length(encoder) > 1) { - websocket.send(encoding.toUint8Array(encoder)) - } - } - websocket.onerror = (event) => { - provider.emit('connection-error', [event, provider]) - } - websocket.onclose = (event) => { - provider.emit('connection-close', [event, provider]) - provider.ws = null - provider.wsconnecting = false - if (provider.wsconnected) { - provider.wsconnected = false - provider.synced = false - // update awareness (all users except local left) - awarenessProtocol.removeAwarenessStates( - provider.awareness, - Array.from(provider.awareness.getStates().keys()).filter((client) => - client !== provider.doc.clientID - ), - provider - ) - provider.emit('status', [{ - status: 'disconnected' - }]) - } else { - provider.wsUnsuccessfulReconnects++ - } - // Start with no reconnect timeout and increase timeout by - // using exponential backoff starting with 100ms - setTimeout( - setupWS, - math.min( - math.pow(2, provider.wsUnsuccessfulReconnects) * 100, - provider.maxBackoffTime - ), - provider - ) - } - websocket.onopen = () => { - provider.wsLastMessageReceived = time.getUnixTime() - provider.wsconnecting = false - provider.wsconnected = true - provider.wsUnsuccessfulReconnects = 0 - provider.emit('status', [{ - status: 'connected' - }]) - // always send sync step 1 when connected - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeSyncStep1(encoder, provider.doc) - websocket.send(encoding.toUint8Array(encoder)) - // broadcast local awareness state - if (provider.awareness.getLocalState() !== null) { - const encoderAwarenessState = encoding.createEncoder() - encoding.writeVarUint(encoderAwarenessState, messageAwareness) - encoding.writeVarUint8Array( - encoderAwarenessState, - awarenessProtocol.encodeAwarenessUpdate(provider.awareness, [ - provider.doc.clientID - ]) - ) - websocket.send(encoding.toUint8Array(encoderAwarenessState)) - } - } - provider.emit('status', [{ - status: 'connecting' - }]) - } + websocket.onmessage = (event) => { + provider.wsLastMessageReceived = time.getUnixTime() + const encoder = readMessage(provider, new Uint8Array(event.data), true) + if (encoding.length(encoder) > 1) { + websocket.send(encoding.toUint8Array(encoder)) + } + } + websocket.onerror = (event) => { + provider.emit('connection-error', [event, provider]) + } + websocket.onclose = (event) => { + provider.emit('connection-close', [event, provider]) + provider.ws = null + provider.wsconnecting = false + if (provider.wsconnected) { + provider.wsconnected = false + provider.synced = false + // update awareness (all users except local left) + awarenessProtocol.removeAwarenessStates( + provider.awareness, + Array.from(provider.awareness.getStates().keys()).filter((client) => + client !== provider.doc.clientID, + ), + provider, + ) + provider.emit('status', [{ + status: 'disconnected', + }]) + } else { + provider.wsUnsuccessfulReconnects++ + } + // Start with no reconnect timeout and increase timeout by + // using exponential backoff starting with 100ms + setTimeout( + setupWS, + math.min( + math.pow(2, provider.wsUnsuccessfulReconnects) * 100, + provider.maxBackoffTime, + ), + provider, + ) + } + websocket.onopen = () => { + provider.wsLastMessageReceived = time.getUnixTime() + provider.wsconnecting = false + provider.wsconnected = true + provider.wsUnsuccessfulReconnects = 0 + provider.emit('status', [{ + status: 'connected', + }]) + // always send sync step 1 when connected + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageSync) + syncProtocol.writeSyncStep1(encoder, provider.doc) + websocket.send(encoding.toUint8Array(encoder)) + // broadcast local awareness state + if (provider.awareness.getLocalState() !== null) { + const encoderAwarenessState = encoding.createEncoder() + encoding.writeVarUint(encoderAwarenessState, messageAwareness) + encoding.writeVarUint8Array( + encoderAwarenessState, + awarenessProtocol.encodeAwarenessUpdate(provider.awareness, [ + provider.doc.clientID, + ]), + ) + websocket.send(encoding.toUint8Array(encoderAwarenessState)) + } + } + provider.emit('status', [{ + status: 'connecting', + }]) + } } /** @@ -215,13 +216,13 @@ const setupWS = (provider) => { * @param {ArrayBuffer} buf */ const broadcastMessage = (provider, buf) => { - const ws = provider.ws - if (provider.wsconnected && ws && ws.readyState === ws.OPEN) { - ws.send(buf) - } - if (provider.bcconnected) { - bc.publish(provider.bcChannel, buf, provider) - } + const ws = provider.ws + if (provider.wsconnected && ws && ws.readyState === ws.OPEN) { + ws.send(buf) + } + if (provider.bcconnected) { + bc.publish(provider.bcChannel, buf, provider) + } } /** @@ -235,264 +236,266 @@ const broadcastMessage = (provider, buf) => { * const doc = new Y.Doc() * const provider = new WebsocketProvider('http://localhost:1234', 'my-document-name', doc) * - * @extends {Observable} + * @augments {Observable} */ export class WebsocketProvider extends Observable { - /** - * @param {string} serverUrl - * @param {string} roomname - * @param {Y.Doc} doc - * @param {object} opts - * @param {boolean} [opts.connect] - * @param {awarenessProtocol.Awareness} [opts.awareness] - * @param {Object} [opts.params] specify url parameters - * @param {Array} [opts.protocols] specify websocket protocols - * @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionall provide a WebSocket polyfill - * @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds - * @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff) - * @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication - */ - constructor (serverUrl, roomname, doc, { - connect = true, - awareness = new awarenessProtocol.Awareness(doc), - params = {}, - protocols = [], - WebSocketPolyfill = WebSocket, - resyncInterval = -1, - maxBackoffTime = 2500, - disableBc = false - } = {}) { - super() - // ensure that url is always ends with / - while (serverUrl[serverUrl.length - 1] === '/') { - serverUrl = serverUrl.slice(0, serverUrl.length - 1) - } - this.serverUrl = serverUrl - this.bcChannel = serverUrl + '/' + roomname - this.maxBackoffTime = maxBackoffTime - /** - * The specified url parameters. This can be safely updated. The changed parameters will be used - * when a new connection is established. - * @type {Object} - */ - this.params = params - this.protocols = protocols - this.roomname = roomname - this.doc = doc - this._WS = WebSocketPolyfill - this.awareness = awareness - this.wsconnected = false - this.wsconnecting = false - this.bcconnected = false - this.disableBc = disableBc - this.wsUnsuccessfulReconnects = 0 - this.messageHandlers = messageHandlers.slice() - /** - * @type {boolean} - */ - this._synced = false - /** - * @type {WebSocket?} - */ - this.ws = null - this.wsLastMessageReceived = 0 - /** - * Whether to connect to other peers or not - * @type {boolean} - */ - this.shouldConnect = connect - /** - * @type {number} - */ - this._resyncInterval = 0 - if (resyncInterval > 0) { - this._resyncInterval = /** @type {any} */ (setInterval(() => { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { - // resend sync step 1 - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeSyncStep1(encoder, doc) - this.ws.send(encoding.toUint8Array(encoder)) - } - }, resyncInterval)) - } + /** + * @param {string} serverUrl + * @param {string} roomname + * @param {Y.Doc} doc + * @param {object} opts + * @param {boolean} [opts.connect] + * @param {awarenessProtocol.Awareness} [opts.awareness] + * @param {{[key: string]: string}} [opts.params] specify url parameters + * @param {Array} [opts.protocols] specify websocket protocols + * @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionall provide a WebSocket polyfill + * @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds + * @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff) + * @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication + */ + constructor(serverUrl, roomname, doc, { + connect = true, + awareness = new awarenessProtocol.Awareness(doc), + params = {}, + protocols = [], + WebSocketPolyfill = WebSocket, + resyncInterval = -1, + maxBackoffTime = 2500, + disableBc = false, + } = {}) { + super() + // ensure that url is always ends with / + while (serverUrl[serverUrl.length - 1] === '/') { + serverUrl = serverUrl.slice(0, serverUrl.length - 1) + } + this.serverUrl = serverUrl + this.bcChannel = serverUrl + '/' + roomname + this.maxBackoffTime = maxBackoffTime + /** + * The specified url parameters. This can be safely updated. The changed parameters will be used + * when a new connection is established. + * @type {{[key: string]: string}} + */ + this.params = params + this.protocols = protocols + this.roomname = roomname + this.doc = doc + this._WS = WebSocketPolyfill + this.awareness = awareness + this.wsconnected = false + this.wsconnecting = false + this.bcconnected = false + this.disableBc = disableBc + this.wsUnsuccessfulReconnects = 0 + this.messageHandlers = messageHandlers.slice() + /** + * @type {boolean} + */ + this._synced = false + /** + * @type {WebSocket?} + */ + this.ws = null + this.wsLastMessageReceived = 0 + /** + * Whether to connect to other peers or not + * @type {boolean} + */ + this.shouldConnect = connect - /** - * @param {ArrayBuffer} data - * @param {any} origin - */ - this._bcSubscriber = (data, origin) => { - if (origin !== this) { - const encoder = readMessage(this, new Uint8Array(data), false) - if (encoding.length(encoder) > 1) { - bc.publish(this.bcChannel, encoding.toUint8Array(encoder), this) - } - } - } - /** - * Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel) - * @param {Uint8Array} update - * @param {any} origin - */ - this._updateHandler = (update, origin) => { - if (origin !== this) { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageSync) - syncProtocol.writeUpdate(encoder, update) - broadcastMessage(this, encoding.toUint8Array(encoder)) - } - } - this.doc.on('update', this._updateHandler) - /** - * @param {any} changed - * @param {any} _origin - */ - this._awarenessUpdateHandler = ({ added, updated, removed }, _origin) => { - const changedClients = added.concat(updated).concat(removed) - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array( - encoder, - awarenessProtocol.encodeAwarenessUpdate(awareness, changedClients) - ) - broadcastMessage(this, encoding.toUint8Array(encoder)) - } - this._exitHandler = () => { - awarenessProtocol.removeAwarenessStates( - this.awareness, - [doc.clientID], - 'app closed' - ) - } - if (env.isNode && typeof process !== 'undefined') { - process.on('exit', this._exitHandler) - } - awareness.on('update', this._awarenessUpdateHandler) - this._checkInterval = /** @type {any} */ (setInterval(() => { - if ( - this.wsconnected && - messageReconnectTimeout < - time.getUnixTime() - this.wsLastMessageReceived - ) { - // no message received in a long time - not even your own awareness - // updates (which are updated every 15 seconds) - /** @type {WebSocket} */ (this.ws).close() - } - }, messageReconnectTimeout / 10)) - if (connect) { - this.connect() - } - } + /** + * @type {number} + */ + this._resyncInterval = 0 + if (resyncInterval > 0) { + this._resyncInterval = /** @type {any} */ (setInterval(() => { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + // resend sync step 1 + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageSync) + syncProtocol.writeSyncStep1(encoder, doc) + this.ws.send(encoding.toUint8Array(encoder)) + } + }, resyncInterval)) + } - get url () { - const encodedParams = url.encodeQueryParams(this.params) - return this.serverUrl + '/' + this.roomname + - (encodedParams.length === 0 ? '' : '?' + encodedParams) - } + /** + * @param {ArrayBuffer} data + * @param {any} origin + */ + this._bcSubscriber = (data, origin) => { + if (origin !== this) { + const encoder = readMessage(this, new Uint8Array(data), false) + if (encoding.length(encoder) > 1) { + bc.publish(this.bcChannel, encoding.toUint8Array(encoder), this) + } + } + } + /** + * Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel) + * @param {Uint8Array} update + * @param {any} origin + */ + this._updateHandler = (update, origin) => { + if (origin !== this) { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageSync) + syncProtocol.writeUpdate(encoder, update) + broadcastMessage(this, encoding.toUint8Array(encoder)) + } + } + this.doc.on('update', this._updateHandler) + /** + * @param {any} changed + * @param {any} _origin + */ + this._awarenessUpdateHandler = ({ added, updated, removed }, _origin) => { + const changedClients = added.concat(updated).concat(removed) + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageAwareness) + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate(awareness, changedClients), + ) + broadcastMessage(this, encoding.toUint8Array(encoder)) + } + this._exitHandler = () => { + awarenessProtocol.removeAwarenessStates( + this.awareness, + [doc.clientID], + 'app closed', + ) + } + if (env.isNode && typeof process !== 'undefined') { + process.on('exit', this._exitHandler) + } + awareness.on('update', this._awarenessUpdateHandler) + this._checkInterval = /** @type {any} */ (setInterval(() => { + if ( + this.wsconnected + && messageReconnectTimeout + < time.getUnixTime() - this.wsLastMessageReceived + ) { + // no message received in a long time - not even your own awareness + // updates (which are updated every 15 seconds) + /** @type {WebSocket} */ (this.ws).close() + } + }, messageReconnectTimeout / 10)) + if (connect) { + this.connect() + } + } - /** - * @type {boolean} - */ - get synced () { - return this._synced - } + get url() { + const encodedParams = url.encodeQueryParams(this.params) + return this.serverUrl + '/' + this.roomname + + (encodedParams.length === 0 ? '' : '?' + encodedParams) + } - set synced (state) { - if (this._synced !== state) { - this._synced = state - this.emit('synced', [state]) - this.emit('sync', [state]) - } - } + /** + * @type {boolean} + */ + get synced() { + return this._synced + } - destroy () { - if (this._resyncInterval !== 0) { - clearInterval(this._resyncInterval) - } - clearInterval(this._checkInterval) - this.disconnect() - if (env.isNode && typeof process !== 'undefined') { - process.off('exit', this._exitHandler) - } - this.awareness.off('update', this._awarenessUpdateHandler) - this.doc.off('update', this._updateHandler) - super.destroy() - } + set synced(state) { + if (this._synced !== state) { + this._synced = state + this.emit('synced', [state]) + this.emit('sync', [state]) + } + } - connectBc () { - if (this.disableBc) { - return - } - if (!this.bcconnected) { - bc.subscribe(this.bcChannel, this._bcSubscriber) - this.bcconnected = true - } - // send sync step1 to bc - // write sync step 1 - const encoderSync = encoding.createEncoder() - encoding.writeVarUint(encoderSync, messageSync) - syncProtocol.writeSyncStep1(encoderSync, this.doc) - bc.publish(this.bcChannel, encoding.toUint8Array(encoderSync), this) - // broadcast local state - const encoderState = encoding.createEncoder() - encoding.writeVarUint(encoderState, messageSync) - syncProtocol.writeSyncStep2(encoderState, this.doc) - bc.publish(this.bcChannel, encoding.toUint8Array(encoderState), this) - // write queryAwareness - const encoderAwarenessQuery = encoding.createEncoder() - encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness) - bc.publish( - this.bcChannel, - encoding.toUint8Array(encoderAwarenessQuery), - this - ) - // broadcast local awareness state - const encoderAwarenessState = encoding.createEncoder() - encoding.writeVarUint(encoderAwarenessState, messageAwareness) - encoding.writeVarUint8Array( - encoderAwarenessState, - awarenessProtocol.encodeAwarenessUpdate(this.awareness, [ - this.doc.clientID - ]) - ) - bc.publish( - this.bcChannel, - encoding.toUint8Array(encoderAwarenessState), - this - ) - } + destroy() { + if (this._resyncInterval !== 0) { + clearInterval(this._resyncInterval) + } + clearInterval(this._checkInterval) + this.disconnect() + if (env.isNode && typeof process !== 'undefined') { + process.off('exit', this._exitHandler) + } + this.awareness.off('update', this._awarenessUpdateHandler) + this.doc.off('update', this._updateHandler) + super.destroy() + } - disconnectBc () { - // broadcast message with local awareness state set to null (indicating disconnect) - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, messageAwareness) - encoding.writeVarUint8Array( - encoder, - awarenessProtocol.encodeAwarenessUpdate(this.awareness, [ - this.doc.clientID - ], new Map()) - ) - broadcastMessage(this, encoding.toUint8Array(encoder)) - if (this.bcconnected) { - bc.unsubscribe(this.bcChannel, this._bcSubscriber) - this.bcconnected = false - } - } + connectBc() { + if (this.disableBc) { + return + } + if (!this.bcconnected) { + bc.subscribe(this.bcChannel, this._bcSubscriber) + this.bcconnected = true + } + // send sync step1 to bc + // write sync step 1 + const encoderSync = encoding.createEncoder() + encoding.writeVarUint(encoderSync, messageSync) + syncProtocol.writeSyncStep1(encoderSync, this.doc) + bc.publish(this.bcChannel, encoding.toUint8Array(encoderSync), this) + // broadcast local state + const encoderState = encoding.createEncoder() + encoding.writeVarUint(encoderState, messageSync) + syncProtocol.writeSyncStep2(encoderState, this.doc) + bc.publish(this.bcChannel, encoding.toUint8Array(encoderState), this) + // write queryAwareness + const encoderAwarenessQuery = encoding.createEncoder() + encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness) + bc.publish( + this.bcChannel, + encoding.toUint8Array(encoderAwarenessQuery), + this, + ) + // broadcast local awareness state + const encoderAwarenessState = encoding.createEncoder() + encoding.writeVarUint(encoderAwarenessState, messageAwareness) + encoding.writeVarUint8Array( + encoderAwarenessState, + awarenessProtocol.encodeAwarenessUpdate(this.awareness, [ + this.doc.clientID, + ]), + ) + bc.publish( + this.bcChannel, + encoding.toUint8Array(encoderAwarenessState), + this, + ) + } - disconnect () { - this.shouldConnect = false - this.disconnectBc() - if (this.ws !== null) { - this.ws.close() - } - } + disconnectBc() { + // broadcast message with local awareness state set to null (indicating disconnect) + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, messageAwareness) + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate(this.awareness, [ + this.doc.clientID, + ], new Map()), + ) + broadcastMessage(this, encoding.toUint8Array(encoder)) + if (this.bcconnected) { + bc.unsubscribe(this.bcChannel, this._bcSubscriber) + this.bcconnected = false + } + } + + disconnect() { + this.shouldConnect = false + this.disconnectBc() + if (this.ws !== null) { + this.ws.close() + } + } + + connect() { + this.shouldConnect = true + if (!this.wsconnected && this.ws === null) { + setupWS(this) + this.connectBc() + } + } - connect () { - this.shouldConnect = true - if (!this.wsconnected && this.ws === null) { - setupWS(this) - this.connectBc() - } - } }