2020-04-13 10:21:44 -04:00
|
|
|
import { EventEmitter } from 'events'
|
|
|
|
import { logger } from '../logger'
|
2020-05-09 13:58:13 -04:00
|
|
|
import { SocketWithNoteId } from '../realtime'
|
2020-04-13 10:21:44 -04:00
|
|
|
import Selection from './selection'
|
|
|
|
import Server from './server'
|
|
|
|
import TextOperation from './text-operation'
|
|
|
|
import WrappedOperation from './wrapped-operation'
|
|
|
|
|
|
|
|
export class EditorSocketIOServer extends Server {
|
|
|
|
private readonly users: {}
|
|
|
|
private readonly docId: any
|
2020-05-09 13:58:13 -04:00
|
|
|
private mayWrite: (socket: SocketWithNoteId, originIsOperation: boolean, callback: (mayEdit: boolean) => void) => void
|
2020-04-13 10:21:44 -04:00
|
|
|
|
|
|
|
constructor (document, operations, docId, mayWrite, operationCallback) {
|
|
|
|
super(document, operations)
|
|
|
|
// Whatever that does?
|
|
|
|
EventEmitter.call(this)
|
|
|
|
this.users = {}
|
|
|
|
this.docId = docId
|
2020-05-09 13:58:13 -04:00
|
|
|
this.mayWrite = mayWrite || function (_, originIsOperation, cb) {
|
2020-04-13 10:21:44 -04:00
|
|
|
cb(true)
|
|
|
|
}
|
|
|
|
this.operationCallback = operationCallback
|
|
|
|
}
|
|
|
|
|
|
|
|
addClient (socket) {
|
|
|
|
const self = this
|
|
|
|
socket.join(this.docId)
|
|
|
|
const docOut = {
|
|
|
|
str: this.document,
|
|
|
|
revision: this.operations.length,
|
|
|
|
clients: this.users
|
|
|
|
}
|
|
|
|
socket.emit('doc', docOut)
|
|
|
|
socket.on('operation', function (revision, operation, selection) {
|
2020-05-09 13:58:13 -04:00
|
|
|
self.mayWrite(socket, true, function (mayWrite) {
|
2020-04-13 10:21:44 -04:00
|
|
|
if (!mayWrite) {
|
|
|
|
logger.info("User doesn't have the right to edit.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
self.onOperation(socket, revision, operation, selection)
|
|
|
|
if (typeof self.operationCallback === 'function')
|
|
|
|
self.operationCallback(socket, operation)
|
|
|
|
} catch (err) {
|
|
|
|
setTimeout(function () {
|
|
|
|
const docOut = {
|
|
|
|
str: self.document,
|
|
|
|
revision: self.operations.length,
|
|
|
|
clients: self.users,
|
|
|
|
force: true
|
|
|
|
}
|
|
|
|
socket.emit('doc', docOut)
|
|
|
|
}, 100)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
socket.on('get_operations', function (base, head) {
|
|
|
|
self.onGetOperations(socket, base, head)
|
|
|
|
})
|
|
|
|
socket.on('selection', function (obj) {
|
2020-05-09 13:58:13 -04:00
|
|
|
self.mayWrite(socket, false, function (mayWrite) {
|
2020-04-13 10:21:44 -04:00
|
|
|
if (!mayWrite) {
|
|
|
|
logger.info("User doesn't have the right to edit.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
self.updateSelection(socket, obj && Selection.fromJSON(obj))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
socket.on('disconnect', function () {
|
|
|
|
logger.debug("Disconnect")
|
|
|
|
socket.leave(self.docId)
|
|
|
|
self.onDisconnect(socket)
|
|
|
|
/*
|
|
|
|
if (socket.manager && socket.manager.sockets.clients(self.docId).length === 0) {
|
|
|
|
self.emit('empty-room');
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
})
|
|
|
|
};
|
|
|
|
|
|
|
|
onOperation (socket, revision, operation, selection) {
|
|
|
|
let wrapped
|
|
|
|
try {
|
|
|
|
wrapped = new WrappedOperation(
|
|
|
|
TextOperation.fromJSON(operation),
|
|
|
|
selection && Selection.fromJSON(selection)
|
|
|
|
)
|
|
|
|
} catch (exc) {
|
|
|
|
logger.error("Invalid operation received: ")
|
|
|
|
logger.error(exc)
|
|
|
|
throw new Error(exc)
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const clientId = socket.id
|
|
|
|
const wrappedPrime = this.receiveOperation(revision, wrapped)
|
|
|
|
if (!wrappedPrime) return
|
|
|
|
logger.debug("new operation: " + JSON.stringify(wrapped))
|
|
|
|
this.getClient(clientId).selection = wrappedPrime.meta
|
|
|
|
revision = this.operations.length
|
|
|
|
socket.emit('ack', revision)
|
|
|
|
socket.broadcast.in(this.docId).emit(
|
|
|
|
'operation', clientId, revision,
|
|
|
|
wrappedPrime.wrapped.toJSON(), wrappedPrime.meta
|
|
|
|
)
|
|
|
|
//set document is dirty
|
|
|
|
this.isDirty = true
|
|
|
|
} catch (exc) {
|
|
|
|
logger.error(exc)
|
|
|
|
throw new Error(exc)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
onGetOperations (socket, base, head) {
|
|
|
|
const operations = this.operations.slice(base, head).map(function (op) {
|
|
|
|
return op.wrapped.toJSON()
|
|
|
|
})
|
|
|
|
socket.emit('operations', head, operations)
|
|
|
|
};
|
|
|
|
|
|
|
|
updateSelection (socket, selection) {
|
|
|
|
const clientId = socket.id
|
|
|
|
if (selection) {
|
|
|
|
this.getClient(clientId).selection = selection
|
|
|
|
} else {
|
|
|
|
delete this.getClient(clientId).selection
|
|
|
|
}
|
|
|
|
socket.broadcast.to(this.docId).emit('selection', clientId, selection)
|
|
|
|
};
|
|
|
|
|
|
|
|
setName (socket, name) {
|
|
|
|
const clientId = socket.id
|
|
|
|
this.getClient(clientId).name = name
|
|
|
|
socket.broadcast.to(this.docId).emit('set_name', clientId, name)
|
|
|
|
};
|
|
|
|
|
|
|
|
setColor (socket, color) {
|
|
|
|
const clientId = socket.id
|
|
|
|
this.getClient(clientId).color = color
|
|
|
|
socket.broadcast.to(this.docId).emit('set_color', clientId, color)
|
|
|
|
};
|
|
|
|
|
|
|
|
getClient (clientId) {
|
|
|
|
return this.users[clientId] || (this.users[clientId] = {})
|
|
|
|
};
|
|
|
|
|
|
|
|
onDisconnect (socket) {
|
|
|
|
const clientId = socket.id
|
|
|
|
delete this.users[clientId]
|
|
|
|
socket.broadcast.to(this.docId).emit('client_left', clientId)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|