fix(web): critical fixes — preserve tokens on disconnect, clean up forceRemoveEntry, update registry on reconnect failure
- disconnectInstance: save cached tokens BEFORE filtering the instance out, so reconnectInstance can restore disconnected entries from localStorage - reconnectInstance: if instance is not in active instances array, attempt to restore it from cached localStorage token before proceeding with reconnect - reconnectInstance: update registry to unreachable/auth_expired on failure - forceRemoveEntry: now tears down WS, removes from instances array, purges token from localStorage, and cleans up space store (was registry-only before)
This commit is contained in:
@@ -437,12 +437,17 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
const registryUpdatedAt = Date.now();
|
const registryUpdatedAt = Date.now();
|
||||||
|
|
||||||
set((state) => {
|
// Save the token BEFORE filtering it out of instances — this preserves
|
||||||
const updated = state.instances.filter(i => i.origin !== origin);
|
// the cached token in localStorage so reconnectInstance can restore it later.
|
||||||
const userId = useAuthStore.getState().user?.id;
|
const currentInstances = get().instances;
|
||||||
if (userId) saveCachedTokens(updated, userId);
|
const userId = useAuthStore.getState().user?.id;
|
||||||
return { instances: updated, registry, registryUpdatedAt };
|
if (userId) saveCachedTokens(currentInstances, userId);
|
||||||
});
|
|
||||||
|
set((state) => ({
|
||||||
|
instances: state.instances.filter(i => i.origin !== origin),
|
||||||
|
registry,
|
||||||
|
registryUpdatedAt,
|
||||||
|
}));
|
||||||
|
|
||||||
// Remove spaces from this instance from the space store
|
// Remove spaces from this instance from the space store
|
||||||
useSpaceStore.getState().removeInstanceSpaces(origin);
|
useSpaceStore.getState().removeInstanceSpaces(origin);
|
||||||
@@ -453,8 +458,39 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
reconnectInstance: async (origin: string) => {
|
reconnectInstance: async (origin: string) => {
|
||||||
const inst = get().instances.find(i => i.origin === origin);
|
let inst = get().instances.find(i => i.origin === origin);
|
||||||
if (!inst || inst.status === 'connected' || inst.status === 'connecting') return;
|
|
||||||
|
// If the instance was disconnected (removed from active instances array) but
|
||||||
|
// has a cached token in localStorage, restore it so reconnect can proceed.
|
||||||
|
if (!inst) {
|
||||||
|
const userId = useAuthStore.getState().user?.id;
|
||||||
|
if (!userId) return;
|
||||||
|
const cached = loadCachedTokens(userId);
|
||||||
|
const entry = cached[origin];
|
||||||
|
if (!entry?.token) return; // No cached token — needs full re-authentication via connectToRemote
|
||||||
|
|
||||||
|
const currentUser = useAuthStore.getState().user;
|
||||||
|
if (!currentUser) return;
|
||||||
|
|
||||||
|
const client = createApiClient(origin, () => entry.token);
|
||||||
|
const restoredInstance: ConnectedInstance = {
|
||||||
|
origin,
|
||||||
|
label: entry.label || new URL(origin).host,
|
||||||
|
token: entry.token,
|
||||||
|
user: currentUser,
|
||||||
|
username: entry.username || '',
|
||||||
|
status: 'connecting' as const,
|
||||||
|
api: client,
|
||||||
|
};
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
instances: [...state.instances, restoredInstance],
|
||||||
|
}));
|
||||||
|
|
||||||
|
inst = restoredInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inst.status === 'connected' || inst.status === 'connecting') return;
|
||||||
|
|
||||||
// Tokenless placeholders can't reconnect — they need full re-authentication
|
// Tokenless placeholders can't reconnect — they need full re-authentication
|
||||||
if (!inst.token) return;
|
if (!inst.token) return;
|
||||||
@@ -497,6 +533,15 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
: i
|
: i
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Update registry on network error
|
||||||
|
const errRegistry = upsertRegistryEntry(get().registry, origin, {
|
||||||
|
origin,
|
||||||
|
status: 'unreachable',
|
||||||
|
errorMessage: 'Instance unreachable',
|
||||||
|
});
|
||||||
|
set({ registry: errRegistry, registryUpdatedAt: Date.now() });
|
||||||
|
|
||||||
connectInstance(origin, inst.token);
|
connectInstance(origin, inst.token);
|
||||||
} else {
|
} else {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
@@ -506,6 +551,14 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
: i
|
: i
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Update registry on auth error
|
||||||
|
const errRegistry = upsertRegistryEntry(get().registry, origin, {
|
||||||
|
origin,
|
||||||
|
status: 'auth_expired',
|
||||||
|
errorMessage: 'Token expired',
|
||||||
|
});
|
||||||
|
set({ registry: errRegistry, registryUpdatedAt: Date.now() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -655,10 +708,25 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
forceRemoveEntry: (origin: string) => {
|
forceRemoveEntry: (origin: string) => {
|
||||||
|
// Tear down WebSocket if connected
|
||||||
|
disconnectWs(origin);
|
||||||
|
|
||||||
|
// Remove from registry
|
||||||
const registry = new Map(get().registry);
|
const registry = new Map(get().registry);
|
||||||
registry.delete(origin);
|
registry.delete(origin);
|
||||||
const registryUpdatedAt = Date.now();
|
const registryUpdatedAt = Date.now();
|
||||||
set({ registry, registryUpdatedAt });
|
|
||||||
|
// Remove from instances and purge token from localStorage
|
||||||
|
set((state) => {
|
||||||
|
const updated = state.instances.filter(i => i.origin !== origin);
|
||||||
|
const userId = useAuthStore.getState().user?.id;
|
||||||
|
if (userId) saveCachedTokens(updated, userId);
|
||||||
|
return { instances: updated, registry, registryUpdatedAt };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up spaces belonging to this instance
|
||||||
|
useSpaceStore.getState().removeInstanceSpaces(origin);
|
||||||
|
|
||||||
get().syncRegistry().catch(() => {});
|
get().syncRegistry().catch(() => {});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user