compatible with yjs@v13-79

This commit is contained in:
Kevin Jahns
2019-04-24 08:07:37 +02:00
parent 9208e699e2
commit 01519447ca
6 changed files with 14846 additions and 69 deletions

View File

@@ -3,18 +3,29 @@ Unlike stated in the LICENSE file, it is not necessary to include the copyright
*/
const Y = require('yjs')
const syncProtocol = require('y-protocols/dist/sync.js')
const awarenessProtocol = require('y-protocols/dist/awareness.js')
/**
* @type {any}
*/
const WebSocket = require('ws')
const http = require('http')
const encoding = require('lib0/dist/encoding.js')
const decoding = require('lib0/dist/decoding.js')
const mutex = require('lib0/dist/mutex.js')
const map = require('lib0/dist/map.js')
const port = process.env.PORT || 1234
// disable gc when using snapshots!
const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0'
const persistenceDir = process.env.YPERSISTENCE
/**
* @type {{bindState: function(string,WSSharedDoc):void, writeState:function(string,WSSharedDoc):Promise}|null}
*/
let persistence = null
if (typeof persistenceDir === 'string') {
// @ts-ignore
const LevelDbPersistence = require('y-leveldb').LevelDbPersistence
persistence = new LevelDbPersistence(persistenceDir)
}
@@ -26,17 +37,24 @@ const server = http.createServer((req, res) => {
const wss = new WebSocket.Server({ noServer: true })
/**
* @type {Map<number,WSSharedDoc>}
*/
const docs = new Map()
const messageSync = 0
const messageAwareness = 1
const messageAuth = 2
// const messageAuth = 2
const afterTransaction = (doc, transaction) => {
if (transaction.encodedStructsLen > 0) {
/**
* @param {Y.Transaction} transaction
* @param {WSSharedDoc} doc
*/
const afterTransaction = (transaction, doc) => {
if (transaction.updateMessage !== null) {
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageSync)
Y.syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
syncProtocol.writeUpdate(encoder, transaction.updateMessage)
const message = encoding.toBuffer(encoder)
doc.conns.forEach((_, conn) => conn.send(message))
}
@@ -45,18 +63,29 @@ const afterTransaction = (doc, transaction) => {
class WSSharedDoc extends Y.Y {
constructor () {
super({ gc: gcEnabled })
this.mux = Y.createMutex()
this.mux = mutex.createMutex()
/**
* Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed
* @type {Map<Object, Set<number>>}
*/
this.conns = new Map()
/**
* @type {Map<number,Object>}
*/
this.awareness = new Map()
/**
* @type {Map<number,number>}
*/
this.awarenessClock = new Map()
this.on('afterTransaction', afterTransaction)
}
}
/**
* @param {any} conn
* @param {WSSharedDoc} doc
* @param {ArrayBuffer} message
*/
const messageListener = (conn, doc, message) => {
const encoder = encoding.createEncoder()
const decoder = decoding.createDecoder(message)
@@ -64,18 +93,19 @@ const messageListener = (conn, doc, message) => {
switch (messageType) {
case messageSync:
encoding.writeVarUint(encoder, messageSync)
Y.syncProtocol.readSyncMessage(decoder, encoder, doc)
syncProtocol.readSyncMessage(decoder, encoder, doc)
if (encoding.length(encoder) > 1) {
conn.send(encoding.toBuffer(encoder))
}
break
case messageAwareness: {
encoding.writeVarUint(encoder, messageAwareness)
const updates = Y.awarenessProtocol.forwardAwarenessMessage(decoder, encoder)
const updates = awarenessProtocol.forwardAwarenessMessage(decoder, encoder)
updates.forEach(update => {
doc.awareness.set(update.userID, update.state)
doc.awarenessClock.set(update.userID, update.clock)
doc.conns.get(conn).add(update.userID)
doc.awareness.set(update.clientID, update.state)
doc.awarenessClock.set(update.clientID, update.clock)
// @ts-ignore we received an update => so conn exists
doc.conns.get(conn).add(update.clientID)
})
const buff = encoding.toBuffer(encoder)
doc.conns.forEach((_, c) => {
@@ -86,31 +116,40 @@ const messageListener = (conn, doc, message) => {
}
}
/**
* @param {any} conn
* @param {any} req
*/
const setupConnection = (conn, req) => {
conn.binaryType = 'arraybuffer'
// get doc, create if it does not exist yet
const docName = req.url.slice(1)
let doc = docs.get(docName)
if (doc === undefined) {
doc = new WSSharedDoc()
const doc = map.setIfUndefined(docs, docName, () => {
const doc = new WSSharedDoc()
if (persistence !== null) {
persistence.bindState(docName, doc)
}
docs.set(docName, doc)
}
return doc
})
doc.conns.set(conn, new Set())
// listen and reply to events
// @ts-ignore
conn.on('message', message => messageListener(conn, doc, message))
conn.on('close', () => {
/**
* @type {Set<number>}
*/
// @ts-ignore
const controlledIds = doc.conns.get(conn)
doc.conns.delete(conn)
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageAwareness)
Y.awarenessProtocol.writeUsersStateChange(encoder, Array.from(controlledIds).map(userID => {
const clock = (doc.awarenessClock.get(userID) || 0) + 1
doc.awareness.delete(userID)
doc.awarenessClock.delete(userID)
return { userID, state: null, clock }
awarenessProtocol.writeUsersStateChange(encoder, Array.from(controlledIds).map(clientID => {
const clock = (doc.awarenessClock.get(clientID) || 0) + 1
doc.awareness.delete(clientID)
doc.awarenessClock.delete(clientID)
return { clientID, state: null, clock }
}))
const buf = encoding.toBuffer(encoder)
doc.conns.forEach((_, conn) => conn.send(buf))
@@ -125,16 +164,19 @@ const setupConnection = (conn, req) => {
// send sync step 1
const encoder = encoding.createEncoder()
encoding.writeVarUint(encoder, messageSync)
Y.syncProtocol.writeSyncStep1(encoder, doc)
syncProtocol.writeSyncStep1(encoder, doc.store)
conn.send(encoding.toBuffer(encoder))
if (doc.awareness.size > 0) {
const encoder = encoding.createEncoder()
/**
* @type {Array<Object>}
*/
const userStates = []
doc.awareness.forEach((state, userID) => {
userStates.push({ state, userID, clock: (doc.awarenessClock.get(userID) || 0) })
doc.awareness.forEach((state, clientID) => {
userStates.push({ state, clientID, clock: (doc.awarenessClock.get(clientID) || 0) })
})
encoding.writeVarUint(encoder, messageAwareness)
Y.awarenessProtocol.writeUsersStateChange(encoder, userStates)
awarenessProtocol.writeUsersStateChange(encoder, userStates)
conn.send(encoding.toBuffer(encoder))
}
}
@@ -143,9 +185,13 @@ wss.on('connection', setupConnection)
server.on('upgrade', (request, socket, head) => {
// You may check auth of request here..
wss.handleUpgrade(request, socket, head, ws => {
/**
* @param {any} ws
*/
const handleAuth = ws => {
wss.emit('connection', ws, request)
})
}
wss.handleUpgrade(request, socket, head, handleAuth)
})
server.listen(port)