From eebf920a9d257be66cb53a6119e3dd2b6db065f4 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Fri, 2 Jun 2023 13:43:49 +0200 Subject: [PATCH] Refactoring --- web/cm_plugins/collab.ts | 37 ++++++++++++++++++++++++++------- web/editor.tsx | 44 +++++++++------------------------------- web/sync_service.ts | 10 +++++++++ web/syscalls/collab.ts | 2 +- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/web/cm_plugins/collab.ts b/web/cm_plugins/collab.ts index 1a4ef0c2..0604c79d 100644 --- a/web/cm_plugins/collab.ts +++ b/web/cm_plugins/collab.ts @@ -1,4 +1,5 @@ import { Extension, HocuspocusProvider, Y, yCollab } from "../deps.ts"; +import { SyncService } from "../sync_service.ts"; const userColors = [ { color: "#30bced", light: "#30bced33" }, @@ -17,15 +18,31 @@ export class CollabState { private yundoManager: Y.UndoManager; constructor( - serverUrl: string, - name: string, - username: string, - onStateless: (data: any) => any, + private serverUrl: string, + private pageName: string, + private token: string, + private username: string, + private syncService: SyncService, ) { this.collabProvider = new HocuspocusProvider({ url: serverUrl, - name: name, - onStateless, + name: token, + + // Receive broadcasted messages from the server (right now only "page has been persisted" notifications) + onStateless: ( + { payload }, + ) => { + const message = JSON.parse(payload); + switch (message.type) { + case "persisted": { + // Received remote persist notification, updating snapshot + syncService.updateRemoteLastModified( + message.path, + message.lastModified, + ).catch(console.error); + } + } + }, }); this.collabProvider.on("status", (e: any) => { @@ -46,12 +63,18 @@ export class CollabState { } stop() { - // this.collabProvider.disconnect(); console.log("[COLLAB] Destroying collab provider"); this.collabProvider.destroy(); // For whatever reason, destroy() doesn't properly clean up everything so we need to help a bit this.collabProvider.configuration.websocketProvider.webSocket = null; this.collabProvider.configuration.websocketProvider.destroy(); + + // When stopping collaboration, we're going back to sync mode. Make sure we got the latest and greatest remote timestamp to avoid + // conflicts + this.syncService.fetchAndPersistRemoteLastModified(`${this.pageName}.md`) + .catch( + console.error, + ); } collabExtension(): Extension { diff --git a/web/editor.tsx b/web/editor.tsx index c51d3594..328943dc 100644 --- a/web/editor.tsx +++ b/web/editor.tsx @@ -1155,7 +1155,7 @@ export class Editor { await this.save(true); // And stop the collab session if (this.collabState) { - this.stopCollab(previousPage); + this.stopCollab(); } } } @@ -1525,49 +1525,25 @@ export class Editor { this.collabState.stop(); } const initialText = this.editorView!.state.sliceDoc(); - this.collabState = new CollabState(serverUrl, token, username, ( - { payload }, - ) => { - const message = JSON.parse(payload); - switch (message.type) { - case "persisted": { - // console.log( - // "Received remote persist notification, updating snapshot", - // message, - // ); - this.syncService.updateRemoteLastModified( - message.path, - message.lastModified, - ).catch(console.error); - } - } - }); + this.collabState = new CollabState( + serverUrl, + this.currentPage!, + token, + username, + this.syncService, + ); - // this.collabState.collabProvider.on("synced", () => { - // if (this.collabState?.ytext.toString() === "") { - // console.error("Synced value is empty, putting back original text"); - // this.collabState?.ytext.insert(0, initialText); - // } - // }); this.rebuildEditorState(); + // Don't watch for local changes in this mode this.space.unwatch(); } - stopCollab(pageName: string) { + stopCollab() { if (this.collabState) { this.collabState.stop(); this.collabState = undefined; this.rebuildEditorState(); - // Switching off collab mode - this.syncService.remoteSpace.getFileMeta(`${pageName}.md`).then( - (meta) => { - return this.syncService.updateRemoteLastModified( - meta.name, - meta.lastModified, - ); - }, - ).catch(console.error); } // Start file watching again this.space.watch(); diff --git a/web/sync_service.ts b/web/sync_service.ts index 203b9d72..6b23f3e1 100644 --- a/web/sync_service.ts +++ b/web/sync_service.ts @@ -165,6 +165,16 @@ export class SyncService { await this.registerSyncStop(); } + // Reach out out to remote space, fetch the latest lastModified time and update the local snapshot + // This is used when exiting collab mode + async fetchAndPersistRemoteLastModified(path: string) { + const meta = await this.remoteSpace.getFileMeta(path); + await this.updateRemoteLastModified( + meta.name, + meta.lastModified, + ); + } + // When in collab mode, we delegate the sync to the CDRT engine, to avoid conflicts, we try to keep the lastModified time in sync when local changes happen async updateLocalLastModified(path: string, lastModified: number) { await this.noOngoingSync(); diff --git a/web/syscalls/collab.ts b/web/syscalls/collab.ts index 6239b55b..ac1493d6 100644 --- a/web/syscalls/collab.ts +++ b/web/syscalls/collab.ts @@ -14,7 +14,7 @@ export function collabSyscalls(editor: Editor): SysCallMapping { "collab.stop": ( _ctx, ) => { - editor.stopCollab(editor.currentPage!); + editor.stopCollab(); }, "collab.ping": async ( _ctx,