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]);
|
}, [session.authed, session.username]);
|
||||||
|
|
||||||
// Load connections when authenticated
|
// Load connections when authenticated, clear when logged out
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadConnections = async () => {
|
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);
|
setConnectionsLoading(true);
|
||||||
try {
|
try {
|
||||||
const myConnections = await getMyConnections();
|
const myConnections = await getMyConnections();
|
||||||
setConnections(myConnections as UserConnectionWithProfile[]);
|
setConnections(myConnections as UserConnectionWithProfile[]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load connections:', error);
|
console.error('Failed to load connections:', error);
|
||||||
|
setConnections([]); // Clear on error too
|
||||||
} finally {
|
} finally {
|
||||||
setConnectionsLoading(false);
|
setConnectionsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +133,23 @@ const CryptIDDropdown: React.FC<CryptIDDropdownProps> = ({ isDarkMode = false })
|
||||||
loadConnections();
|
loadConnections();
|
||||||
}, [session.authed, session.username]);
|
}, [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
|
// Connection handlers
|
||||||
const handleConnect = async (userId: string, trustLevel: TrustLevel) => {
|
const handleConnect = async (userId: string, trustLevel: TrustLevel) => {
|
||||||
if (!session.authed || !session.username) return;
|
if (!session.authed || !session.username) return;
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,24 @@ export function useNetworkGraph(options: UseNetworkGraphOptions = {}): UseNetwor
|
||||||
fetchGraph();
|
fetchGraph();
|
||||||
}, [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
|
// Refresh interval
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refreshInterval > 0) {
|
if (refreshInterval > 0) {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,16 @@ export const saveSession = (session: Session): boolean => {
|
||||||
};
|
};
|
||||||
|
|
||||||
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(storedSession));
|
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;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('🔧 Error saving session:', 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 => {
|
export const clearStoredSession = (): boolean => {
|
||||||
if (typeof window === 'undefined') return false;
|
if (typeof window === 'undefined') return false;
|
||||||
|
|
||||||
try {
|
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);
|
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;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('🔧 Error clearing session:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue