fix: resolve user identity caching issues on logout/login
- Preserve tldraw-user-id-* and crypto keys on logout (prevents duplicate cursors) - Add session-logged-in event for immediate tool enabling after login - Add session-cleared event for component state cleanup - Clear only session-specific data (permissions, graph cache, room ID) - CryptIDDropdown: reset connections state on logout - useNetworkGraph: clear graph cache on logout The key fix is preserving tldraw user IDs across login/logout cycles. Previously, clearing these IDs caused each login to create a new presence record while old ones persisted in Automerge, resulting in stacked cursors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
65eee48665
commit
e94ceb39c9
|
|
@ -110,16 +110,22 @@ const CryptIDDropdown: React.FC<CryptIDDropdownProps> = ({ isDarkMode = false })
|
|||
}
|
||||
}, [session.authed, session.username]);
|
||||
|
||||
// Load connections when authenticated
|
||||
// Load connections when authenticated, clear when logged out
|
||||
useEffect(() => {
|
||||
const loadConnections = async () => {
|
||||
if (!session.authed || !session.username) return;
|
||||
if (!session.authed || !session.username) {
|
||||
// Clear connections state when user logs out
|
||||
setConnections([]);
|
||||
setConnectionsLoading(false);
|
||||
return;
|
||||
}
|
||||
setConnectionsLoading(true);
|
||||
try {
|
||||
const myConnections = await getMyConnections();
|
||||
setConnections(myConnections as UserConnectionWithProfile[]);
|
||||
} catch (error) {
|
||||
console.error('Failed to load connections:', error);
|
||||
setConnections([]); // Clear on error too
|
||||
} finally {
|
||||
setConnectionsLoading(false);
|
||||
}
|
||||
|
|
@ -127,6 +133,23 @@ const CryptIDDropdown: React.FC<CryptIDDropdownProps> = ({ isDarkMode = false })
|
|||
loadConnections();
|
||||
}, [session.authed, session.username]);
|
||||
|
||||
// Listen for session-cleared event to immediately clear connections state
|
||||
useEffect(() => {
|
||||
const handleSessionCleared = () => {
|
||||
console.log('🔐 CryptIDDropdown: Session cleared - resetting connections state');
|
||||
setConnections([]);
|
||||
setConnectionsLoading(false);
|
||||
setShowDropdown(false);
|
||||
setShowCryptIDModal(false);
|
||||
setExpandedSection('none');
|
||||
setEditingConnectionId(null);
|
||||
setEditingMetadata({});
|
||||
};
|
||||
|
||||
window.addEventListener('session-cleared', handleSessionCleared);
|
||||
return () => window.removeEventListener('session-cleared', handleSessionCleared);
|
||||
}, []);
|
||||
|
||||
// Connection handlers
|
||||
const handleConnect = async (userId: string, trustLevel: TrustLevel) => {
|
||||
if (!session.authed || !session.username) return;
|
||||
|
|
|
|||
|
|
@ -310,6 +310,24 @@ export function useNetworkGraph(options: UseNetworkGraphOptions = {}): UseNetwor
|
|||
fetchGraph();
|
||||
}, [fetchGraph]);
|
||||
|
||||
// Listen for session-cleared event to immediately clear graph state
|
||||
useEffect(() => {
|
||||
const handleSessionCleared = () => {
|
||||
console.log('🔐 useNetworkGraph: Session cleared - resetting graph state');
|
||||
clearGraphCache();
|
||||
setState({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
myConnections: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('session-cleared', handleSessionCleared);
|
||||
return () => window.removeEventListener('session-cleared', handleSessionCleared);
|
||||
}, []);
|
||||
|
||||
// Refresh interval
|
||||
useEffect(() => {
|
||||
if (refreshInterval > 0) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export interface StoredSession {
|
|||
*/
|
||||
export const saveSession = (session: Session): boolean => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
|
||||
try {
|
||||
const storedSession: StoredSession = {
|
||||
username: session.username,
|
||||
|
|
@ -28,8 +28,18 @@ export const saveSession = (session: Session): boolean => {
|
|||
obsidianVaultPath: session.obsidianVaultPath,
|
||||
obsidianVaultName: session.obsidianVaultName
|
||||
};
|
||||
|
||||
|
||||
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(storedSession));
|
||||
|
||||
// Dispatch event to notify components that session was saved (e.g., after login)
|
||||
// This helps components like Board.tsx update their state immediately
|
||||
if (session.authed && session.username) {
|
||||
window.dispatchEvent(new CustomEvent('session-logged-in', {
|
||||
detail: { username: session.username }
|
||||
}));
|
||||
console.log('🔐 Session saved and session-logged-in event dispatched for:', session.username);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('🔧 Error saving session:', error);
|
||||
|
|
@ -72,15 +82,57 @@ export const loadSession = (): StoredSession | null => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Clear stored session
|
||||
* Clear stored session and all related user data
|
||||
* This ensures a clean slate when logging out
|
||||
*/
|
||||
export const clearStoredSession = (): boolean => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
|
||||
try {
|
||||
// Get the current username before clearing (to clean up user-specific keys)
|
||||
const currentSession = loadSession();
|
||||
const username = currentSession?.username;
|
||||
|
||||
// Clear the main session
|
||||
localStorage.removeItem(SESSION_STORAGE_KEY);
|
||||
|
||||
// IMPORTANT: Do NOT clear tldraw-user-id-* keys!
|
||||
// These must persist so the same user keeps the same presence ID across login/logout cycles.
|
||||
// If we clear them, each login creates a NEW presence ID, but old presence records
|
||||
// persist in the shared Automerge document and sync back - causing stacked cursors.
|
||||
|
||||
// IMPORTANT: Do NOT clear crypto keys or user account data - they must persist for re-login!
|
||||
// The following are PRESERVED across logout (tied to user ACCOUNT, not session):
|
||||
// - tldraw-user-id-${username} (presence ID - must stay same to avoid duplicate cursors)
|
||||
// - ${username}_authData (crypto challenge/signature required for login)
|
||||
// - ${username}_publicKey (public key for verification)
|
||||
// - ${username}_fathomApiKey (integration credentials)
|
||||
// - ${username}_miroApiKey (integration credentials)
|
||||
// - registeredUsers (list of registered accounts on this device)
|
||||
//
|
||||
// Only SESSION-SPECIFIC data is cleared below:
|
||||
|
||||
// Clear any cached permission data (session-specific)
|
||||
localStorage.removeItem('boardPermissions');
|
||||
localStorage.removeItem('currentBoardPermission');
|
||||
|
||||
// Clear network graph cache to force fresh state (session-specific)
|
||||
localStorage.removeItem('network_graph_cache');
|
||||
|
||||
// Clear current room ID to prevent stale room associations (session-specific)
|
||||
localStorage.removeItem('currentRoomId');
|
||||
|
||||
// Dispatch event to notify all components to clear their state
|
||||
// This helps ensure components like CryptIDDropdown, NetworkGraphPanel, etc.
|
||||
// properly reset their internal state
|
||||
window.dispatchEvent(new CustomEvent('session-cleared', {
|
||||
detail: { previousUsername: username }
|
||||
}));
|
||||
|
||||
console.log('🔐 Session cleared - removed session state, preserved account data (crypto keys, tldraw IDs)');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('🔧 Error clearing session:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue