fix(realtime): уход на вход после отзыва сессии и бан в открытом сервере

- invalidateCurrentUser перечитывает профиль, а не только удаляет кэш: без
  запроса AuthGuard не видит 401 и второе устройство остаётся в приложении
  после logout-all или смены пароля (AGENT.md 11.6, пункт 12);
- сброс сессии и уход из последнего сервера инвалидируют список серверов:
  пустой снапшот подставлял устаревший REST-ответ, и сервер возвращался в
  рейку (видно и после кика);
- joinGuild проверяет бан сервера: публичный сервер больше не обходится
  нажатием «Войти» вместо приглашения (AGENT.md 7.17, 7.20), тест расширен;
- e2e/voice-ten приведён к линту, console.log разрешён в e2e (диагностика
  прогона), артефакты Playwright исключены из prettier.
This commit is contained in:
2026-09-22 20:35:13 +03:00
parent b2a48d8008
commit 7ec9dbfdee
6 changed files with 55 additions and 48 deletions
+16 -1
View File
@@ -189,6 +189,9 @@ export function dispatchGatewayEvent(event: GatewayDispatch): void {
// removeGuild сам выбирает первый оставшийся сервер (или пустой список).
session.removeGuild(guildId);
void invalidateGuildQueries(guildId);
// Снапшот и REST-кэш должны совпадать: иначе пустой стор подставит
// устаревший myGuilds и сервер вернётся в рейку.
void invalidateMyGuilds();
syncUrlWithSelection();
return;
}
@@ -354,6 +357,7 @@ export function dispatchGatewayEvent(event: GatewayDispatch): void {
session.reset();
messages.reset();
useVoiceConnectionStore.getState().reset();
void invalidateMyGuilds();
void invalidateCurrentUser();
return;
}
@@ -490,6 +494,15 @@ async function invalidateMembershipQueries(guildId: string): Promise<void> {
]);
}
/** Инвалидация списка серверов: держит REST-кэш в согласии со снапшотом. */
async function invalidateMyGuilds(): Promise<void> {
const [{ getQueryClient }, users] = await Promise.all([
import('@/lib/queryClient'),
import('@/api/users'),
]);
await getQueryClient().invalidateQueries({ queryKey: users.myGuildsQueryKey });
}
/** Инвалидация данных удалённого сервера (карточка, участники, каналы). */
async function invalidateGuildQueries(guildId: string): Promise<void> {
const [{ getQueryClient }, guilds] = await Promise.all([
@@ -555,8 +568,10 @@ async function invalidateCurrentUser(): Promise<void> {
import('@/lib/queryClient'),
import('@/api/users'),
]);
// removeQueries вместо invalidate: сессия мертва, повторный запрос не нужен.
// Именно refetch, а не removeQueries: AuthGuard узнаёт о мёртвой сессии по
// 401 на запросе профиля и только тогда уводит на экран входа (AGENT.md 11.6).
getQueryClient().removeQueries({ queryKey: currentUserQueryKey });
await getQueryClient().refetchQueries({ queryKey: currentUserQueryKey });
}
async function refetchCurrentUser(): Promise<void> {