UNPKG

webgme-ot

Version:

Modified version of https://github.com/operational-transformation/ot.js to fit into webgme framework

212 lines (172 loc) 7.37 kB
// translation of https://github.com/djspiewak/cccp/blob/master/agent/src/main/scala/com/codecommit/cccp/agent/state.scala var log = function (){}; function Client(revision) { this.revision = revision; // the next expected revision number this.state = synchronized_; // start state } Client.prototype.setState = function (state) { this.state = state; this.state.logState(this); }; // Call this method when the user changes the document. Client.prototype.applyClient = function (operation) { this.setState(this.state.applyClient(this, operation)); }; // Call this method with a new operation from the server Client.prototype.applyServer = function (operation) { this.revision++; this.setState(this.state.applyServer(this, operation)); }; Client.prototype.serverAck = function () { this.revision++; this.setState(this.state.serverAck(this)); }; Client.prototype.serverReconnect = function () { if (typeof this.state.resend === 'function') { this.state.resend(this); } }; // Transforms a selection from the latest known server state to the current // client state. For example, if we get from the server the information that // another user's cursor is at position 3, but the server hasn't yet received // our newest operation, an insertion of 5 characters at the beginning of the // document, the correct position of the other user's cursor in our current // document is 8. Client.prototype.transformSelection = function (selection) { return this.state.transformSelection(selection); }; // Override this method. Client.prototype.sendOperation = function (revision, operation) { throw new Error("sendOperation must be defined in child class"); }; // Override this method. Client.prototype.applyOperation = function (operation) { throw new Error("applyOperation must be defined in child class"); }; // In the 'Synchronized' state, there is no pending operation that the client // has sent to the server. function Synchronized() { } Client.Synchronized = Synchronized; Synchronized.prototype.applyClient = function (client, operation) { // When the user makes an edit, send the operation to the server and // switch to the 'AwaitingConfirm' state client.sendOperation(client.revision, operation); return new AwaitingConfirm(operation); }; Synchronized.prototype.applyServer = function (client, operation) { // When we receive a new operation from the server, the operation can be // simply applied to the current document client.applyOperation(operation); return this; }; Synchronized.prototype.serverAck = function (client) { throw new Error("There is no pending operation."); }; // Nothing to do because the latest server state and client state are the same. Synchronized.prototype.transformSelection = function (x) { return x; }; Synchronized.prototype.logState = function (client) { log('Synchronized at revision', client.revision); }; // Singleton var synchronized_ = new Synchronized(); // In the 'AwaitingConfirm' state, there's one operation the client has sent // to the server and is still waiting for an acknowledgement. function AwaitingConfirm(outstanding) { // Save the pending operation this.outstanding = outstanding; } Client.AwaitingConfirm = AwaitingConfirm; AwaitingConfirm.prototype.applyClient = function (client, operation) { // When the user makes an edit, don't send the operation immediately, // instead switch to 'AwaitingWithBuffer' state return new AwaitingWithBuffer(this.outstanding, operation); }; AwaitingConfirm.prototype.applyServer = function (client, operation) { // This is another client's operation. Visualization: // // /\ // this.outstanding / \ operation // / \ // \ / // pair[1] \ / pair[0] (new outstanding) // (can be applied \/ // to the client's // current document) var pair = operation.constructor.transform(this.outstanding, operation); client.applyOperation(pair[1]); return new AwaitingConfirm(pair[0]); }; AwaitingConfirm.prototype.serverAck = function (client) { // The client's operation has been acknowledged // => switch to synchronized state return synchronized_; }; AwaitingConfirm.prototype.transformSelection = function (selection) { return selection.transform(this.outstanding); }; AwaitingConfirm.prototype.resend = function (client) { // The confirm didn't come because the client was disconnected. // Now that it has reconnected, we resend the outstanding operation. client.sendOperation(client.revision, this.outstanding); }; AwaitingConfirm.prototype.logState = function (client) { log('AwaitingConfirm at revision', client.revision, '\noperation:', this.outstanding); }; // In the 'AwaitingWithBuffer' state, the client is waiting for an operation // to be acknowledged by the server while buffering the edits the user makes function AwaitingWithBuffer(outstanding, buffer) { // Save the pending operation and the user's edits since then this.outstanding = outstanding; this.buffer = buffer; } Client.AwaitingWithBuffer = AwaitingWithBuffer; AwaitingWithBuffer.prototype.applyClient = function (client, operation) { // Compose the user's changes onto the buffer var newBuffer = this.buffer.compose(operation); return new AwaitingWithBuffer(this.outstanding, newBuffer); }; AwaitingWithBuffer.prototype.applyServer = function (client, operation) { // Operation comes from another client // // /\ // this.outstanding / \ operation // / \ // /\ / // this.buffer / \* / pair1[0] (new outstanding) // / \/ // \ / // pair2[1] \ / pair2[0] (new buffer) // the transformed \/ // operation -- can // be applied to the // client's current // document // // * pair1[1] var transform = operation.constructor.transform; var pair1 = transform(this.outstanding, operation); var pair2 = transform(this.buffer, pair1[1]); client.applyOperation(pair2[1]); return new AwaitingWithBuffer(pair1[0], pair2[0]); }; AwaitingWithBuffer.prototype.serverAck = function (client) { // The pending operation has been acknowledged // => send buffer client.sendOperation(client.revision, this.buffer); return new AwaitingConfirm(this.buffer); }; AwaitingWithBuffer.prototype.transformSelection = function (selection) { return selection.transform(this.outstanding).transform(this.buffer); }; AwaitingWithBuffer.prototype.resend = function (client) { // The confirm didn't come because the client was disconnected. // Now that it has reconnected, we resend the outstanding operation. client.sendOperation(client.revision, this.outstanding); }; AwaitingWithBuffer.prototype.logState = function (client) { log('AwaitingWithBuffer at revision', client.revision, '\noperation:', this.outstanding); }; module.exports = Client;