diff --git a/bin/utils.js b/bin/utils.js index 3f8e9cc..9b96e16 100644 --- a/bin/utils.js +++ b/bin/utils.js @@ -64,6 +64,7 @@ class WSSharedDoc extends Y.Doc { * @type {awarenessProtocol.Awareness} */ this.awareness = new awarenessProtocol.Awareness(this) + this.awareness.setLocalState(null) /** * @param {{ added: Array, updated: Array, removed: Array }} changes * @param {Object | null} conn Origin is the connection that made the change diff --git a/package-lock.json b/package-lock.json index ba6281f..f59b3c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -995,9 +995,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "loose-envify": { @@ -1699,17 +1699,17 @@ "dev": true }, "y-protocols": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.6.tgz", - "integrity": "sha512-XgUBKrFesfUYN3wXmVp9Exy7dOUOeX3A56gHNuI1ZNy9N7OdwoBv2TGfbvSH6+YpV1IEvEq7u5v0/je5MwXKJg==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.7.tgz", + "integrity": "sha512-vimoF3v/H9kMRBKQcbYppObK6FUITPL+huyvbYdPhLiLTSSm5dS7/BT1E/Wyl488CyoPoZTxiOqfu1WphwTe6A==", "requires": { "lib0": "0.0.5" } }, "yjs": { - "version": "13.0.0-83", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.0.0-83.tgz", - "integrity": "sha512-8M1X8fZ95odf2VU8BHrKcYr0PeEsx8tspV5svh4oLp8BVcIprbp0J2hzCvJDlOFOlyJVgvNf00UJ4uiyDKmk5A==", + "version": "13.0.0-94", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.0.0-94.tgz", + "integrity": "sha512-4HETCv/BZ4Mg9rZU+nLZXW8GGp9ugWDA1ImcyIW2hZA2n0YsIgwvROPqWpflwi/l02kfLlvLKF8nEXYk/cOsjQ==", "dev": true, "requires": { "lib0": "0.0.5" diff --git a/package.json b/package.json index b2fd0ca..4daa191 100644 --- a/package.json +++ b/package.json @@ -40,17 +40,17 @@ ] }, "dependencies": { - "y-protocols": "0.0.6", + "y-protocols": "0.0.7", "lib0": "0.0.5" }, "devDependencies": { "rollup": "^1.1.2", "rollup-cli": "^1.0.9", "standard": "^12.0.1", - "yjs": "13.0.0-83" + "yjs": "13.0.0-94" }, "peerDependenies": { - "yjs": ">=13.0.0-83" + "yjs": ">=13.0.0-94" }, "optionalDependencies": { "ws": "^6.2.1" diff --git a/src/y-websocket.js b/src/y-websocket.js index 2d2bfa9..a504fed 100644 --- a/src/y-websocket.js +++ b/src/y-websocket.js @@ -10,6 +10,7 @@ Unlike stated in the LICENSE file, it is not necessary to include the copyright import * as Y from 'yjs' // eslint-disable-line import * as bc from 'lib0/broadcastchannel.js' +import * as time from 'lib0/time.js' import * as encoding from 'lib0/encoding.js' import * as decoding from 'lib0/decoding.js' import * as syncProtocol from 'y-protocols/sync.js' @@ -17,13 +18,17 @@ import * as authProtocol from 'y-protocols/auth.js' import * as awarenessProtocol from 'y-protocols/awareness.js' import * as mutex from 'lib0/mutex.js' import { Observable } from 'lib0/observable.js' +import * as math from 'lib0/math.js' const messageSync = 0 const messageQueryAwareness = 3 const messageAwareness = 1 const messageAuth = 2 -const reconnectTimeout = 3000 +const reconnectTimeoutBase = 1200 +const maxReconnectTimeout = 2500 +// @todo - this should depend on awareness.outdatedTime +const messageReconnectTimeout = 30000 /** * @param {WebsocketProvider} provider @@ -73,6 +78,7 @@ const setupWS = provider => { provider.wsconnecting = true provider.wsconnected = false websocket.onmessage = event => { + provider.wsLastMessageReceived = time.getUnixTime() const encoder = readMessage(provider, new Uint8Array(event.data)) if (encoding.length(encoder) > 1) { websocket.send(encoding.toUint8Array(encoder)) @@ -81,19 +87,27 @@ const setupWS = provider => { websocket.onclose = () => { provider.ws = null provider.wsconnecting = false - provider.wsconnected = false if (provider.wsconnected) { + provider.wsconnected = false // update awareness (all users left) awarenessProtocol.removeAwarenessStates(provider.awareness, Array.from(provider.awareness.getStates().keys()), provider) provider.emit('status', [{ status: 'disconnected' }]) + } else { + provider.wsUnsuccessfulReconnects++ } - setTimeout(setupWS, reconnectTimeout, provider) + // Start with no reconnect timeout and increase timeout by + // log10(wsUnsuccessfulReconnects). + // The idea is to increase reconnect timeout slowly and have no reconnect + // timeout at the beginning (log(1) = 0) + setTimeout(setupWS, math.min(Math.log10(provider.wsUnsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), provider) } websocket.onopen = () => { + provider.wsLastMessageReceived = time.getUnixTime() provider.wsconnecting = false provider.wsconnected = true + provider.wsUnsuccessfulReconnects = 0 provider.emit('status', [{ status: 'connected' }]) @@ -167,11 +181,13 @@ export class WebsocketProvider extends Observable { this.wsconnected = false this.wsconnecting = false this.bcconnected = false + this.wsUnsuccessfulReconnects = 0 this.mux = mutex.createMutex() /** * @type {WebSocket?} */ this.ws = null + this.wsLastMessageReceived = 0 /** * Whether to connect to other peers or not * @type {boolean} @@ -219,9 +235,17 @@ export class WebsocketProvider extends Observable { broadcastMessage(this, encoding.toUint8Array(encoder)) }) awareness.on('change', this._awarenessUpdateHandler) + this._checkInterval = 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() + } + }) this.connect() } destroy () { + clearInterval(this._checkInterval) this.disconnect() this.awareness.off('change', this._awarenessUpdateHandler) super.destroy()