SSE: событие партии несёт участников, клиент не дёргает лишних
Событие о партии рассылается всей группе, и клиент по нему инвалидировал историю игр, публичные профили и личную статистику получателя. В группе из шести человек любая партия двоих заставляла остальные четыре вкладки перезапрашивать свою историю и открытый профиль, хотя у них ничего не изменилось. Теперь событие несёт participant_ids. Общие витрины (карточка партии, списки и статистика группы, топ и главная) обновляются у всех — рейтинг глобальный, чужая партия действительно двигает топ. История, публичный профиль и личная статистика обновляются только у тех, кто играл, и у зрителей их профилей. При удалении партии участники собираются ДО удаления: каскад уносит их строки вместе с партией, и собранный после список всегда был бы пустым. Это же поведение закреплено тестом. Событие без participant_ids (вкладка открыта до обновления сервера) обрабатывается по-старому, широко: обновление бэкенда не ломает уже открытые страницы. #33 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { matchAffectedKeys, qk } from "../api/queryKeys";
|
||||
import { useMe } from "./auth";
|
||||
|
||||
interface ServerEvent {
|
||||
type: "match" | "match_draft" | "group" | "invitations" | "notifications";
|
||||
match_id?: number;
|
||||
group_id?: number;
|
||||
/** Кто играл в партии: их история и профили протухли, чужие — нет. */
|
||||
participant_ids?: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,6 +19,11 @@ interface ServerEvent {
|
||||
*/
|
||||
export function useServerEvents(enabled: boolean) {
|
||||
const qc = useQueryClient();
|
||||
const { data: me } = useMe();
|
||||
// Свой id — в ref: положив его в зависимости эффекта, мы бы пересоздавали
|
||||
// SSE-соединение каждый раз, когда профиль перезапрашивается.
|
||||
const myId = useRef<number | null>(null);
|
||||
myId.current = me?.id ?? null;
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
const base = import.meta.env.VITE_API_BASE_URL || "";
|
||||
@@ -42,8 +50,24 @@ export function useServerEvents(enabled: boolean) {
|
||||
qc.invalidateQueries({ queryKey: qk.groupMatches(ev.group_id) });
|
||||
qc.invalidateQueries({ queryKey: qk.groupStats(ev.group_id) });
|
||||
}
|
||||
// История игр и публичные профили тоже меняются от чужой партии.
|
||||
for (const key of matchAffectedKeys) qc.invalidateQueries({ queryKey: key });
|
||||
// Общее меняется от любой партии: рейтинг глобальный, и чужая игра двигает топ.
|
||||
qc.invalidateQueries({ queryKey: qk.home });
|
||||
qc.invalidateQueries({ queryKey: qk.leaderboard });
|
||||
if (ev.participant_ids) {
|
||||
// Личные витрины — только у игравших: иначе каждая партия в группе
|
||||
// заставляла бы всех остальных перезапрашивать свою историю.
|
||||
for (const pid of ev.participant_ids) {
|
||||
qc.invalidateQueries({ queryKey: qk.userMatches(pid) });
|
||||
qc.invalidateQueries({ queryKey: qk.publicProfile(pid) });
|
||||
}
|
||||
if (myId.current != null && ev.participant_ids.includes(myId.current)) {
|
||||
qc.invalidateQueries({ queryKey: qk.myStats });
|
||||
}
|
||||
} else {
|
||||
// Событие от бэкенда без списка участников (вкладка открыта до обновления
|
||||
// сервера) — ведём себя как раньше, широко.
|
||||
for (const key of matchAffectedKeys) qc.invalidateQueries({ queryKey: key });
|
||||
}
|
||||
} else if (ev.type === "group") {
|
||||
if (ev.group_id != null) {
|
||||
qc.invalidateQueries({ queryKey: qk.group(ev.group_id) });
|
||||
|
||||
Reference in New Issue
Block a user