fix: buffer operations in Stale/StaleWithBuffer instead of throwing (#6342)

When 5+ users edit concurrently, revision gaps trigger the Stale state
which throws 'Ignored server-side change.' on incoming operations. This
causes document divergence, silent data corruption, and eventually
force-resets that discard local edits.

Changes:
- Stale/StaleWithBuffer.applyServer: buffer incoming operations instead
  of throwing, apply them sequentially after missing ops arrive
- Stale.applyClient: carry pendingOps to StaleWithBuffer on transition
- editor-socketio-server: retry-based force-reset (3 failures) instead
  of immediate nuclear reset on any OT error
- hex2rgb: defensive null checks for old non-hex color records

Signed-off-by: bxff <51504045+bxff@users.noreply.github.com>
This commit is contained in:
bxff
2026-03-16 15:16:47 +08:00
committed by Philip Molares
parent f51f8dcc19
commit 65bf11554a
5 changed files with 90 additions and 10 deletions

View File

@@ -51,14 +51,23 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
self.onOperation(socket, revision, operation, selection);
if (typeof self.operationCallback === 'function')
self.operationCallback(socket, operation);
socket._opRetries = 0;
} catch (err) {
logger.error("Operation error from client " + socket.id + ": " + err.message);
if (!socket._opRetries) socket._opRetries = 0;
socket._opRetries++;
setTimeout(function() {
var docOut = {
str: self.document,
revision: self.operations.length,
clients: self.users,
force: true
clients: self.users
};
// Only force-reset after 3 consecutive failures as a last resort.
// Without force:true, the client will use its Stale recovery to resync.
if (socket._opRetries >= 3) {
docOut.force = true;
socket._opRetries = 0;
}
socket.emit('doc', docOut);
}, 100);
}