feat: Enhance real-time collaboration features with user awareness and document sharing
- Added user information (UserID, UserName, UserAvatar) to Client struct for presence tracking. - Implemented failure handling in the broadcastMessage function to manage send failures and disconnect clients if necessary. - Introduced document ownership and sharing capabilities: - Added OwnerID and Is_Public fields to Document model. - Created DocumentShare model for managing document sharing with permissions. - Implemented functions for creating, listing, and managing document shares in the Postgres store. - Added user management functionality: - Created User model and associated functions for user management in the Postgres store. - Implemented session management with token hashing for security. - Updated database schema with migrations for users, sessions, and document shares. - Enhanced frontend Yjs integration with awareness event logging for user connections and disconnections.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createYjsDocument,
|
||||
destroyYjsDocument,
|
||||
getRandomColor,
|
||||
getRandomName,
|
||||
type YjsProviders,
|
||||
createYjsDocument,
|
||||
destroyYjsDocument,
|
||||
getRandomColor,
|
||||
getRandomName,
|
||||
type YjsProviders,
|
||||
} from "../lib/yjs";
|
||||
import { useAutoSave } from "./useAutoSave";
|
||||
|
||||
@@ -19,7 +19,6 @@ export const useYjsDocument = (documentId: string) => {
|
||||
let mounted = true;
|
||||
let currentProviders: YjsProviders | null = null;
|
||||
|
||||
// Create Yjs document and providers
|
||||
const initializeDocument = async () => {
|
||||
const yjsProviders = await createYjsDocument(documentId);
|
||||
currentProviders = yjsProviders;
|
||||
@@ -30,19 +29,75 @@ export const useYjsDocument = (documentId: string) => {
|
||||
}
|
||||
|
||||
// Set user info for awareness
|
||||
const userName = getRandomName();
|
||||
const userColor = getRandomColor();
|
||||
yjsProviders.awareness.setLocalStateField("user", {
|
||||
name: getRandomName(),
|
||||
color: getRandomColor(),
|
||||
name: userName,
|
||||
color: userColor,
|
||||
});
|
||||
|
||||
// NEW: Add awareness event logging
|
||||
const handleAwarenessChange = ({
|
||||
added,
|
||||
updated,
|
||||
removed,
|
||||
}: {
|
||||
added: number[];
|
||||
updated: number[];
|
||||
removed: number[];
|
||||
}) => {
|
||||
const states = yjsProviders.awareness.getStates();
|
||||
|
||||
added.forEach((clientId) => {
|
||||
const state = states.get(clientId);
|
||||
const user = state?.user;
|
||||
console.log(
|
||||
`[Awareness] User connected: ${
|
||||
user?.name || "Unknown"
|
||||
} (ID: ${clientId})`,
|
||||
{
|
||||
color: user?.color,
|
||||
clientId,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
updated.forEach((clientId) => {
|
||||
const state = states.get(clientId);
|
||||
const user = state?.user;
|
||||
console.log(
|
||||
`[Awareness] User updated: ${
|
||||
user?.name || "Unknown"
|
||||
} (ID: ${clientId})`
|
||||
);
|
||||
});
|
||||
|
||||
removed.forEach((clientId) => {
|
||||
console.log(`[Awareness] User disconnected (ID: ${clientId})`);
|
||||
});
|
||||
|
||||
console.log(`[Awareness] Total connected users: ${states.size}`);
|
||||
};
|
||||
|
||||
yjsProviders.awareness.on("change", handleAwarenessChange);
|
||||
|
||||
// Listen for sync status
|
||||
yjsProviders.indexeddbProvider.on("synced", () => {
|
||||
console.log("IndexedDB synced");
|
||||
setSynced(true);
|
||||
});
|
||||
|
||||
yjsProviders.websocketProvider.on("status", (event: { status: string }) => {
|
||||
console.log("WebSocket status:", event.status);
|
||||
yjsProviders.websocketProvider.on(
|
||||
"status",
|
||||
(event: { status: string }) => {
|
||||
console.log("WebSocket status:", event.status);
|
||||
}
|
||||
);
|
||||
|
||||
// Log local user info
|
||||
console.log(`[Awareness] Local user initialized: ${userName}`, {
|
||||
color: userColor,
|
||||
clientId: yjsProviders.awareness.clientID,
|
||||
});
|
||||
|
||||
setProviders(yjsProviders);
|
||||
@@ -54,10 +109,13 @@ export const useYjsDocument = (documentId: string) => {
|
||||
return () => {
|
||||
mounted = false;
|
||||
if (currentProviders) {
|
||||
console.log("[Awareness] Cleaning up local user");
|
||||
currentProviders.awareness.setLocalState(null);
|
||||
destroyYjsDocument(currentProviders);
|
||||
}
|
||||
};
|
||||
}, [documentId]);
|
||||
|
||||
|
||||
return { providers, synced };
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IndexeddbPersistence } from "y-indexeddb";
|
||||
import { Awareness } from "y-protocols/awareness";
|
||||
import { WebsocketProvider } from "y-websocket";
|
||||
import * as Y from "yjs";
|
||||
import { documentsApi } from "../api/document";
|
||||
@@ -9,7 +10,7 @@ export interface YjsProviders {
|
||||
ydoc: Y.Doc;
|
||||
websocketProvider: WebsocketProvider;
|
||||
indexeddbProvider: IndexeddbPersistence;
|
||||
awareness: any;
|
||||
awareness: Awareness;
|
||||
}
|
||||
|
||||
export const createYjsDocument = async (documentId: string): Promise<YjsProviders> => {
|
||||
|
||||
Reference in New Issue
Block a user