import { nanoid } from "https://esm.sh/nanoid@4.0.0"; import type { Editor } from "./editor.tsx"; const collabPingInterval = 2500; export class CollabManager { clientId = nanoid(); localCollabServer: string; constructor(private editor: Editor) { this.localCollabServer = location.protocol === "http:" ? `ws://${location.host}/.ws-collab` : `wss://${location.host}/.ws-collab`; editor.eventHook.addLocalListener( "editor:pageLoaded", (pageName, previousPage) => { console.log("Page loaded", pageName, previousPage); this.updatePresence(pageName, previousPage).catch(console.error); }, ); } start() { setInterval(() => { this.updatePresence(this.editor.currentPage!).catch(console.error); }, collabPingInterval); } async updatePresence(currentPage?: string, previousPage?: string) { try { const resp = await this.editor.remoteSpacePrimitives.authenticatedFetch( this.editor.remoteSpacePrimitives.url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ operation: "presence", clientId: this.clientId, previousPage, currentPage, }), keepalive: true, // important for beforeunload event }, ); const { collabId } = await resp.json(); if (this.editor.collabState && !this.editor.collabState.isLocalCollab) { // We're in a remote collab mode, don't do anything return; } // console.log("Collab ID", collabId); const previousCollabId = this.editor.collabState?.token.split("/")[0]; if (!collabId && this.editor.collabState) { // Stop collab console.log("Stopping collab"); if (this.editor.collabState.path === `${currentPage}.md`) { this.editor.flashNotification( "Other users have left this page, switched back to single-user mode.", ); } this.editor.stopCollab(); } else if (collabId && collabId !== previousCollabId) { // Start collab console.log("Starting collab"); this.editor.flashNotification( "Opening page in multi-user mode.", ); this.editor.startCollab( this.localCollabServer, `${collabId}/${currentPage}.md`, this.editor.getUsername(), true, ); } } catch (e: any) { // console.error("Ping error", e); if ( e.message.toLowerCase().includes("failed") && this.editor.collabState ) { console.log("Offline, stopping collab"); this.editor.stopCollab(); } } } }