summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/index.ts45
-rw-r--r--src/main/overleafSocket.ts18
2 files changed, 63 insertions, 0 deletions
diff --git a/src/main/index.ts b/src/main/index.ts
index 21b6e43..89a04b0 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -620,6 +620,17 @@ ipcMain.handle('ot:connect', async (_e, projectId: string) => {
})
})
+ // Relay collaborator cursor updates to renderer
+ overleafSock.on('serverEvent', (name: string, args: unknown[]) => {
+ if (name === 'clientTracking.clientUpdated') {
+ mainWindow?.webContents.send('cursor:remoteUpdate', args[0])
+ } else if (name === 'clientTracking.clientDisconnected') {
+ mainWindow?.webContents.send('cursor:remoteDisconnected', args[0])
+ } else if (name === 'new-chat-message') {
+ mainWindow?.webContents.send('chat:newMessage', args[0])
+ }
+ })
+
const projectResult = await overleafSock.connect(projectId, overleafSessionCookie)
const { files, docPathMap, pathDocMap, fileRefs, rootFolderId } = walkRootFolder(projectResult.project.rootFolder)
@@ -745,6 +756,40 @@ ipcMain.handle('sync:contentChanged', async (_e, docId: string, content: string)
fileSyncBridge?.onEditorContentChanged(docId, content)
})
+// ── Cursor Tracking ────────────────────────────────────────────
+
+ipcMain.handle('cursor:update', async (_e, docId: string, row: number, column: number) => {
+ overleafSock?.updateCursorPosition(docId, row, column)
+})
+
+ipcMain.handle('cursor:getConnectedUsers', async () => {
+ if (!overleafSock) return []
+ try {
+ return await overleafSock.getConnectedUsers()
+ } catch (e) {
+ console.log('[cursor:getConnectedUsers] error:', e)
+ return []
+ }
+})
+
+// ── Chat ───────────────────────────────────────────────────────
+
+ipcMain.handle('chat:getMessages', async (_e, projectId: string, limit?: number) => {
+ if (!overleafSessionCookie) return { success: false, messages: [] }
+ const result = await overleafFetch(`/project/${projectId}/messages?limit=${limit || 50}`)
+ if (!result.ok) return { success: false, messages: [] }
+ return { success: true, messages: result.data }
+})
+
+ipcMain.handle('chat:sendMessage', async (_e, projectId: string, content: string) => {
+ if (!overleafSessionCookie) return { success: false }
+ const result = await overleafFetch(`/project/${projectId}/messages`, {
+ method: 'POST',
+ body: JSON.stringify({ content })
+ })
+ return { success: result.ok }
+})
+
ipcMain.handle('overleaf:listProjects', async () => {
if (!overleafSessionCookie) return { success: false, message: 'not_logged_in' }
diff --git a/src/main/overleafSocket.ts b/src/main/overleafSocket.ts
index f825c4c..52ac20f 100644
--- a/src/main/overleafSocket.ts
+++ b/src/main/overleafSocket.ts
@@ -92,6 +92,10 @@ export class OverleafSocket extends EventEmitter {
return this._projectData
}
+ get publicId(): string | null {
+ return this._projectData?.publicId || null
+ }
+
private setState(s: ConnectionState) {
this._state = s
this.emit('connectionState', s)
@@ -270,6 +274,20 @@ export class OverleafSocket extends EventEmitter {
this.ws?.send(encodeEvent('applyOtUpdate', [docId, { doc: docId, op: ops, v: version, hash, lastV: version }]))
}
+ /** Get list of connected users with their cursor positions */
+ async getConnectedUsers(): Promise<unknown[]> {
+ const result = await this.emitWithAck('clientTracking.getConnectedUsers', []) as unknown[]
+ // result format: [error, usersArray]
+ const err = result[0]
+ if (err) throw new Error(`getConnectedUsers failed: ${JSON.stringify(err)}`)
+ return (result[1] as unknown[]) || []
+ }
+
+ /** Send our cursor position */
+ updateCursorPosition(docId: string, row: number, column: number): void {
+ this.ws?.send(encodeEvent('clientTracking.updatePosition', [{ row, column, doc_id: docId }]))
+ }
+
disconnect() {
this.shouldReconnect = false
this.stopHeartbeat()