feat: add automatic identity synchronization (#167)
This commit is contained in:
@@ -39,6 +39,7 @@ import { NavigationRail } from "@/components/layout/navigation-rail";
|
||||
import { SidebarAppsModal } from "@/components/layout/sidebar-apps-modal";
|
||||
import { InlineAppView } from "@/components/layout/inline-app-view";
|
||||
import { useSidebarApps } from "@/hooks/use-sidebar-apps";
|
||||
import { useIdentitySync } from "@/hooks/use-identity-sync";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { FilePreviewModal } from "@/components/files/file-preview-modal";
|
||||
import { isFilePreviewable } from "@/lib/file-preview";
|
||||
@@ -77,6 +78,7 @@ export default function Home() {
|
||||
const markAsReadTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const { isAuthenticated, client, logout, checkAuth, isLoading: authLoading, connectionLost, isRateLimited, rateLimitUntil } = useAuthStore();
|
||||
const { identities } = useIdentityStore();
|
||||
useIdentitySync();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRateLimited || !rateLimitUntil) {
|
||||
|
||||
@@ -14,6 +14,10 @@ function useSyncIdentities() {
|
||||
const syncIdentities = useAuthStore((state) => state.syncIdentities);
|
||||
return syncIdentities;
|
||||
}
|
||||
|
||||
function useRefreshIdentities() {
|
||||
return useAuthStore((state) => state.refreshIdentities);
|
||||
}
|
||||
import type { Identity, EmailAddress } from '@/lib/jmap/types';
|
||||
import { toast } from '@/stores/toast-store';
|
||||
import { useFocusTrap } from '@/hooks/use-focus-trap';
|
||||
@@ -49,6 +53,15 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
|
||||
const setPreferredPrimary = useIdentityStore((state) => state.setPreferredPrimary);
|
||||
const syncIdentities = useSyncIdentities();
|
||||
|
||||
const refreshIdentitiesFromServer = useRefreshIdentities();
|
||||
|
||||
// Refresh identities from server whenever the modal is opened
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
refreshIdentitiesFromServer();
|
||||
}
|
||||
}, [isOpen, refreshIdentitiesFromServer]);
|
||||
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
|
||||
// Re-sync identities every 30 minutes while the app is open
|
||||
const SYNC_INTERVAL_MS = 30 * 60 * 1000;
|
||||
|
||||
export function useIdentitySync() {
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
const refreshIdentities = useAuthStore((s) => s.refreshIdentities);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
|
||||
// Sync when the user returns to the tab (e.g. after adding an alias in Stalwart)
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
refreshIdentities();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
const interval = setInterval(refreshIdentities, SYNC_INTERVAL_MS);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isAuthenticated, refreshIdentities]);
|
||||
}
|
||||
@@ -47,6 +47,7 @@ interface AuthState {
|
||||
checkAuth: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
syncIdentities: () => void;
|
||||
refreshIdentities: () => Promise<void>;
|
||||
getClientForAccount: (accountId: string) => JMAPClient | undefined;
|
||||
}
|
||||
|
||||
@@ -1513,6 +1514,18 @@ export const useAuthStore = create<AuthState>()(
|
||||
set({ identities, primaryIdentity });
|
||||
},
|
||||
|
||||
refreshIdentities: async () => {
|
||||
const { client, username } = get();
|
||||
if (!client || !username) return;
|
||||
try {
|
||||
const rawIdentities = await client.getIdentities();
|
||||
const { identities, primaryIdentity } = loadIdentities(rawIdentities, username);
|
||||
set({ identities, primaryIdentity });
|
||||
} catch {
|
||||
// Silently fail — background sync should not surface errors to the user
|
||||
}
|
||||
},
|
||||
|
||||
getClientForAccount: (accountId: string) => {
|
||||
return clients.get(accountId);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user