feat(web): hydrate federation registry from server in autoConnectAll with localStorage migration
This commit is contained in:
@@ -664,13 +664,54 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
|
||||
autoConnectAll: async () => {
|
||||
const currentUser = useAuthStore.getState().user;
|
||||
if (!currentUser || currentUser.replicatedInstances.length === 0) {
|
||||
if (!currentUser) {
|
||||
set({ _autoConnectDone: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = loadCachedTokens(currentUser.id);
|
||||
|
||||
// Fetch server-side registry (source of truth for entry list)
|
||||
let serverRegistry: FederationRegistryEntry[] = [];
|
||||
let serverRegistryUpdatedAt = 0;
|
||||
try {
|
||||
const res = await api.users.getFederationRegistry();
|
||||
serverRegistry = res.registry;
|
||||
serverRegistryUpdatedAt = res.updatedAt;
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch federation registry from home:', err);
|
||||
}
|
||||
|
||||
// Initialize registry from server data
|
||||
const registry = new Map<string, FederationRegistryEntry>();
|
||||
for (const entry of serverRegistry) {
|
||||
registry.set(entry.origin, entry);
|
||||
}
|
||||
|
||||
// Migration: promote localStorage-only entries to registry
|
||||
for (const [origin] of Object.entries(cached)) {
|
||||
if (origin === window.location.origin) continue;
|
||||
if (!registry.has(origin)) {
|
||||
registry.set(origin, {
|
||||
origin,
|
||||
label: cached[origin]?.label || new URL(origin).host,
|
||||
username: cached[origin]?.username || '',
|
||||
remoteUserId: '',
|
||||
status: 'connected',
|
||||
addedAt: Date.now(),
|
||||
lastConnectedAt: Date.now(),
|
||||
disconnectedAt: null,
|
||||
errorMessage: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Early return if there's nothing to connect
|
||||
if (currentUser.replicatedInstances.length === 0 && registry.size === 0) {
|
||||
set({ _autoConnectDone: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// Split server-known instances into two groups:
|
||||
// - withToken: have a cached token → attempt reconnection
|
||||
// - withoutToken: no cached token → add as error placeholder
|
||||
@@ -708,6 +749,14 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
});
|
||||
}
|
||||
|
||||
// Update registry for tokenless placeholders
|
||||
for (const { origin } of withoutToken) {
|
||||
const entry = registry.get(origin);
|
||||
if (entry) {
|
||||
registry.set(origin, { ...entry, status: 'auth_expired', errorMessage: 'Session expired — re-authenticate to reconnect' });
|
||||
}
|
||||
}
|
||||
|
||||
// Connect instances with cached tokens in parallel
|
||||
if (withToken.length > 0) {
|
||||
const results = await Promise.allSettled(
|
||||
@@ -771,6 +820,12 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
instances: state.instances.map(i => i.origin === origin ? connectedInstance : i),
|
||||
}));
|
||||
|
||||
// Update registry entry on successful reconnect
|
||||
const entry = registry.get(origin);
|
||||
if (entry) {
|
||||
registry.set(origin, { ...entry, status: 'connected', lastConnectedAt: Date.now(), disconnectedAt: null, errorMessage: null, remoteUserId: user.id, label });
|
||||
}
|
||||
|
||||
// Open WebSocket connection now that we've verified the token
|
||||
connectInstance(origin, cachedEntry.token);
|
||||
|
||||
@@ -791,6 +846,13 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
: i
|
||||
),
|
||||
}));
|
||||
|
||||
// Update registry entry on network error
|
||||
const entry = registry.get(origin);
|
||||
if (entry) {
|
||||
registry.set(origin, { ...entry, status: 'unreachable', errorMessage: 'Instance unreachable' });
|
||||
}
|
||||
|
||||
// Start WebSocket — its built-in exponential backoff retry will auto-recover
|
||||
// when the network path becomes available (e.g. user switches networks)
|
||||
connectInstance(origin, cachedEntry.token);
|
||||
@@ -803,6 +865,12 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
: i
|
||||
),
|
||||
}));
|
||||
|
||||
// Update registry entry on auth error
|
||||
const entry = registry.get(origin);
|
||||
if (entry) {
|
||||
registry.set(origin, { ...entry, status: 'auth_expired', errorMessage: 'Token expired' });
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -824,7 +892,13 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
||||
const pendingOrigins = Object.entries(freshCached)
|
||||
.filter(([, v]) => v.pendingPasswordSync)
|
||||
.map(([origin]) => origin);
|
||||
set({ _autoConnectDone: true, pendingSyncOrigins: pendingOrigins });
|
||||
|
||||
// Persist reconciled registry
|
||||
const registryUpdatedAt = serverRegistryUpdatedAt > 0 ? Math.max(serverRegistryUpdatedAt, Date.now()) : Date.now();
|
||||
set({ _autoConnectDone: true, pendingSyncOrigins: pendingOrigins, registry, registryUpdatedAt });
|
||||
|
||||
// Sync reconciled registry to all instances
|
||||
get().syncRegistry().catch(() => {});
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
|
||||
Reference in New Issue
Block a user