diff --git a/lib/ot/editor-socketio-server.js b/lib/ot/editor-socketio-server.js deleted file mode 100755 index 5014ac8d2..000000000 --- a/lib/ot/editor-socketio-server.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - -var EventEmitter = require('events').EventEmitter; -var TextOperation = require('./text-operation'); -var WrappedOperation = require('./wrapped-operation'); -var Server = require('./server'); -var Selection = require('./selection'); -var util = require('util'); - -var logger = require('../logger'); - -function EditorSocketIOServer(document, operations, docId, mayWrite, operationCallback) { - EventEmitter.call(this); - Server.call(this, document, operations); - this.users = {}; - this.docId = docId; - this.mayWrite = mayWrite || function (_, cb) { - cb(true); - }; - this.operationCallback = operationCallback; -} - -util.inherits(EditorSocketIOServer, Server); -extend(EditorSocketIOServer.prototype, EventEmitter.prototype); - -function extend(target, source) { - for (var key in source) { - if (source.hasOwnProperty(key)) { - target[key] = source[key]; - } - } -} - -EditorSocketIOServer.prototype.addClient = function (socket) { - var self = this; - socket.join(this.docId); - var docOut = { - str: this.document, - revision: this.operations.length, - clients: this.users - }; - socket.emit('doc', docOut); - socket.on('operation', function (revision, operation, selection) { - socket.origin = 'operation'; - self.mayWrite(socket, function (mayWrite) { - 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() { - var 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) { - socket.origin = 'selection'; - self.mayWrite(socket, function (mayWrite) { - 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'); - } - */ - }); -}; - -EditorSocketIOServer.prototype.onOperation = function (socket, revision, operation, selection) { - var 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 { - var clientId = socket.id; - var 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); - } -}; - -EditorSocketIOServer.prototype.onGetOperations = function (socket, base, head) { - var operations = this.operations.slice(base, head).map(function (op) { - return op.wrapped.toJSON(); - }); - socket.emit('operations', head, operations); -}; - -EditorSocketIOServer.prototype.updateSelection = function (socket, selection) { - var 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); -}; - -EditorSocketIOServer.prototype.setName = function (socket, name) { - var clientId = socket.id; - this.getClient(clientId).name = name; - socket.broadcast.to(this.docId).emit('set_name', clientId, name); -}; - -EditorSocketIOServer.prototype.setColor = function (socket, color) { - var clientId = socket.id; - this.getClient(clientId).color = color; - socket.broadcast.to(this.docId).emit('set_color', clientId, color); -}; - -EditorSocketIOServer.prototype.getClient = function (clientId) { - return this.users[clientId] || (this.users[clientId] = {}); -}; - -EditorSocketIOServer.prototype.onDisconnect = function (socket) { - var clientId = socket.id; - delete this.users[clientId]; - socket.broadcast.to(this.docId).emit('client_left', clientId); -}; - -module.exports = EditorSocketIOServer; diff --git a/lib/ot/editor-socketio-server.ts b/lib/ot/editor-socketio-server.ts new file mode 100755 index 000000000..cb27d7f04 --- /dev/null +++ b/lib/ot/editor-socketio-server.ts @@ -0,0 +1,155 @@ +import { EventEmitter } from 'events' +import { logger } from '../logger' +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 + private mayWrite: any + + constructor (document, operations, docId, mayWrite, operationCallback) { + super(document, operations) + // Whatever that does? + EventEmitter.call(this) + this.users = {} + this.docId = docId + this.mayWrite = mayWrite || function (_, cb) { + 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) { + socket.origin = 'operation' + self.mayWrite(socket, function (mayWrite) { + 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) { + socket.origin = 'selection' + self.mayWrite(socket, function (mayWrite) { + 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) + }; +} + diff --git a/lib/realtime.ts b/lib/realtime.ts index 1934c18ba..c6daaaaa6 100644 --- a/lib/realtime.ts +++ b/lib/realtime.ts @@ -10,6 +10,7 @@ import async from 'async' import cookieParser from 'cookie-parser' import cookie from 'cookie' import Chance from 'chance' +import { EditorSocketIOServer } from './ot/editor-socketio-server' const chance = new Chance() @@ -577,7 +578,7 @@ function startConnection (socket) { const body = note.content const createtime = note.createdAt const updatetime = note.lastchangeAt - const server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback) + const server = new EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback) const authors = {} for (let i = 0; i < note.authors.length; i++) {