summaryrefslogtreecommitdiff
path: root/src/main/index.ts
diff options
context:
space:
mode:
authorhaoyuren <13851610112@163.com>2026-03-13 18:46:46 -0500
committerhaoyuren <13851610112@163.com>2026-03-13 18:46:46 -0500
commit11166a63affc4e95450f677d860c8bfdb8211bd9 (patch)
tree2ad4b0c81cb4df6e982c0f88fa43b2ed65b972fc /src/main/index.ts
parentfa52c3c4d6c21a26c838fa33de2d7fa447f9499e (diff)
Fix Claude Code → Overleaf sync: remove hash from applyOtUpdate, fix OT bugs
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 <noreply@anthropic.com>
Diffstat (limited to 'src/main/index.ts')
-rw-r--r--src/main/index.ts12
1 files changed, 6 insertions, 6 deletions
diff --git a/src/main/index.ts b/src/main/index.ts
index 9be1a74..d4d9b2d 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -625,12 +625,15 @@ ipcMain.handle('ot:connect', async (_e, projectId: string) => {
})
// otUpdateApplied: server acknowledges our op (ack signal for OT client)
+ // Only ack when there's no 'op' field — presence of 'op' means it's a remote update, not our ack
overleafSock.on('serverEvent', (name: string, args: unknown[]) => {
if (name === 'otUpdateApplied') {
- const update = args[0] as { doc?: string; v?: number } | undefined
- if (update?.doc) {
+ const update = args[0] as { doc?: string; op?: unknown[]; v?: number } | undefined
+ if (update?.doc && !update.op) {
sendToRenderer('ot:ack', { docId: update.doc })
}
+ } else if (name === 'otUpdateError') {
+ console.log(`[ot:error] server rejected update:`, JSON.stringify(args).slice(0, 500))
}
})
@@ -662,9 +665,7 @@ ipcMain.handle('ot:connect', async (_e, projectId: string) => {
// Set up file sync bridge for bidirectional sync
const tmpDir = compilationManager.dir
fileSyncBridge = new FileSyncBridge(overleafSock, tmpDir, docPathMap, pathDocMap, fileRefs, mainWindow!, projectId, overleafSessionCookie, overleafCsrfToken)
- fileSyncBridge.start().catch((e) => {
- console.log('[ot:connect] fileSyncBridge start error:', e)
- })
+ await fileSyncBridge.start()
return {
success: true,
@@ -703,7 +704,6 @@ ipcMain.handle('ot:joinDoc', async (_e, docId: string) => {
try {
const result = await overleafSock.joinDoc(docId)
const content = (result.docLines || []).join('\n')
-
// Update compilation manager with doc content
if (compilationManager && overleafSock.projectData) {
const { docPathMap } = walkRootFolder(overleafSock.projectData.project.rootFolder)