diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index f50f7c331..3460983bd 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -615,19 +615,15 @@ const handleUserChanges = async (socket, message) => { // Update the changeset so that it can be applied to the latest revision. while (r < pad.getHeadRevisionNumber()) { r++; - - const c = await pad.getRevisionChangeset(r); - + const {changeset: c, meta: {author: authorId}} = await pad.getRevision(r); + if (changeset === c && thisSession.author === authorId) { + // Assume this is a retransmission of an already applied changeset. + rebasedChangeset = Changeset.identity(Changeset.unpack(changeset).oldLen); + } // At this point, both "c" (from the pad) and "changeset" (from the // client) are relative to revision r - 1. The follow function // rebases "changeset" so that it is relative to revision r // and can be applied after "c". - - // a changeset can be based on an old revision with the same changes in it - // prevent eplite from accepting it TODO: better send the client a NEW_CHANGES - // of that revision - if (baseRev + 1 === r && c === changeset) throw new Error('Changeset already accepted'); - rebasedChangeset = Changeset.follow(c, rebasedChangeset, false, pad.pool); } diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index 38dc22af4..74bc66f9f 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -208,8 +208,9 @@ const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad) } else if (msg.type === 'ACCEPT_COMMIT') { serverMessageTaskQueue.enqueue(() => { const {newRev} = msg; - // newRev will equal rev if the changeset has no net effect (identity changeset, or removing - // and re-adding the same characters with the same attributes). + // newRev will equal rev if the changeset has no net effect (identity changeset, removing + // and re-adding the same characters with the same attributes, or retransmission of an + // already applied changeset). if (![rev, rev + 1].includes(newRev)) { window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${rev + 1}`); // setChannelState("DISCONNECTED", "badmessage_acceptcommit"); diff --git a/src/tests/backend/specs/messages.js b/src/tests/backend/specs/messages.js index 55d3c7a19..4c9f7e66c 100644 --- a/src/tests/backend/specs/messages.js +++ b/src/tests/backend/specs/messages.js @@ -75,12 +75,12 @@ describe(__filename, function () { await assertRejected(); }); - it('retransmission is rejected', async function () { + it('retransmission is accepted, has no effect', async function () { sendUserChanges('Z:1>5+5$hello'); await assertAccepted(rev + 1); --rev; sendUserChanges('Z:1>5+5$hello'); - await assertRejected(); + await assertAccepted(rev + 1); assert.equal(pad.text(), 'hello\n'); });