yjs/src/Connectors/WebsocketsConnector/WebsocketsConnector.mjs

141 lines
4.0 KiB
JavaScript

import BinaryEncoder from '../../Util/Binary/Encoder.mjs'
/* global WebSocket */
import NamedEventHandler from '../../Util/NamedEventHandler.mjs'
import decodeMessage, { messageSS, messageSubscribe, messageStructs } from './decodeMessage.mjs'
import { createMutualExclude } from '../../Util/mutualExclude.mjs'
import { messageCheckUpdateCounter } from './decodeMessage.mjs'
export const STATE_DISCONNECTED = 0
export const STATE_CONNECTED = 1
export default class WebsocketsConnector extends NamedEventHandler {
constructor (url = 'ws://localhost:1234') {
super()
this.url = url
this._state = STATE_DISCONNECTED
this._socket = null
this._rooms = new Map()
this._connectToServer = true
this._reconnectTimeout = 300
this._mutualExclude = createMutualExclude()
this._persistence = null
this.connect()
}
getRoom (roomName) {
return this._rooms.get(roomName) || { y: null, roomName, localUpdateCounter: 1 }
}
syncPersistence (persistence) {
this._persistence = persistence
if (this._state === STATE_CONNECTED) {
persistence.getAllDocuments().then(docs => {
const encoder = new BinaryEncoder()
docs.forEach(doc => {
messageCheckUpdateCounter(doc.roomName, encoder, doc.remoteUpdateCounter)
});
this.send(encoder)
})
}
}
connectY (roomName, y) {
let room = this._rooms.get(roomName)
if (room !== undefined) {
throw new Error('Room is already taken! There can be only one Yjs instance per roomName!')
}
this._rooms.set(roomName, {
roomName,
y,
localUpdateCounter: 1
})
y.on('afterTransaction', (y, transaction) => {
this._mutualExclude(() => {
if (transaction.encodedStructsLen > 0) {
const encoder = new BinaryEncoder()
const room = this._rooms.get(roomName)
messageStructs(roomName, y, encoder, transaction.encodedStructs, ++room.localUpdateCounter)
this.send(encoder)
}
})
})
if (this._state === STATE_CONNECTED) {
const encoder = new BinaryEncoder()
messageSS(roomName, y, encoder)
messageSubscribe(roomName, y, encoder)
this.send(encoder)
}
}
_setState (state) {
this._state = state
this.emit('stateChanged', {
state: this.state
})
}
get state () {
return this._state === STATE_DISCONNECTED ? 'disconnected' : 'connected'
}
_onOpen () {
this._setState(STATE_CONNECTED)
if (this._persistence === null) {
const encoder = new BinaryEncoder()
for (const [roomName, room] of this._rooms) {
const y = room.y
messageSS(roomName, y, encoder)
messageSubscribe(roomName, y, encoder)
}
this.send(encoder)
} else {
this.syncPersistence(this._persistence)
}
}
send (encoder) {
if (encoder.length > 0 && this._socket.readyState === WebSocket.OPEN) {
this._socket.send(encoder.createBuffer())
}
}
_onClose () {
this._setState(STATE_DISCONNECTED)
this._socket = null
if (this._connectToServer) {
setTimeout(() => {
if (this._connectToServer) {
this.connect()
}
}, this._reconnectTimeout)
this.connect()
}
}
_onMessage (message) {
if (message.data.byteLength > 0) {
const reply = decodeMessage(this, message.data, null, false, this._persistence)
this.send(reply)
}
}
disconnect (code = 1000, reason = 'Client manually disconnected') {
const socket = this._socket
this._connectToServer = false
socket.close(code, reason)
}
connect () {
if (this._socket === null) {
const socket = new WebSocket(this.url)
socket.binaryType = 'arraybuffer'
this._socket = socket
this._connectToServer = true
// Connection opened
socket.addEventListener('open', this._onOpen.bind(this))
socket.addEventListener('close', this._onClose.bind(this))
socket.addEventListener('message', this._onMessage.bind(this))
}
}
}