From 11166a63affc4e95450f677d860c8bfdb8211bd9 Mon Sep 17 00:00:00 2001 From: haoyuren <13851610112@163.com> Date: Fri, 13 Mar 2026 18:46:46 -0500 Subject: =?UTF-8?q?Fix=20Claude=20Code=20=E2=86=92=20Overleaf=20sync:=20re?= =?UTF-8?q?move=20hash=20from=20applyOtUpdate,=20fix=20OT=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: SHA-1 hash sent with applyOtUpdate didn't match Overleaf's server-side computation, causing "Invalid hash" error, disconnect, and rollback of all synced changes. - Remove hash field from applyOtUpdate to skip server-side hash check - Switch applyOtUpdate from fire-and-forget to emitWithAck for reliable ack - Fix getOldDoc bug: save base doc when changes start accumulating instead of incorrectly using current doc (caused wrong delete ops) - Fix ack handler: only ack when no 'op' field (was acking remote ops too) - Await fileSyncBridge.start() instead of fire-and-forget - Add joinDoc retry logic for transient joinLeaveEpoch mismatch errors - Clear pending ack callbacks on WebSocket close to prevent timeout errors - Add otUpdateError logging for server-side rejections - Add file-based bridge logging for debugging sync issues Co-Authored-By: Claude Opus 4.6 --- src/main/overleafSocket.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/main/overleafSocket.ts') diff --git a/src/main/overleafSocket.ts b/src/main/overleafSocket.ts index 811e433..195eb05 100644 --- a/src/main/overleafSocket.ts +++ b/src/main/overleafSocket.ts @@ -155,6 +155,10 @@ export class OverleafSocket extends EventEmitter { this.ws.on('close', () => { this.stopHeartbeat() + // Clear pending ack callbacks to prevent timeout errors after reconnect + for (const [id, cb] of this.ackCallbacks) { + this.ackCallbacks.delete(id) + } if (this._state === 'connected' && this.shouldReconnect) { this.scheduleReconnect() } @@ -298,8 +302,10 @@ export class OverleafSocket extends EventEmitter { } async applyOtUpdate(docId: string, ops: unknown[], version: number, hash: string): Promise { - // Fire-and-forget: server responds with otUpdateApplied or otUpdateError event - this.ws?.send(encodeEvent('applyOtUpdate', [docId, { doc: docId, op: ops, v: version, hash, lastV: version }])) + // Use emitWithAck so the server's callback response comes back as a Socket.IO ack + // Do NOT send hash — Overleaf's document-updater hash check causes disconnect + rollback on mismatch + const result = await this.emitWithAck('applyOtUpdate', [docId, { doc: docId, op: ops, v: version }]) + if (result) console.log(`[applyOtUpdate] ack for ${docId} v=${version}`) } /** Get list of connected users with their cursor positions */ -- cgit v1.2.3