feat: 用 SSE 替代 SWR 轮询,实现房间状态实时推送

SSE 断连时自动降级为 2s 轮询,重连后切回 SSE。
This commit is contained in:
2026-02-24 19:51:30 +08:00
parent f6949a062f
commit 8c0d89af6d
9 changed files with 223 additions and 71 deletions
+69
View File
@@ -0,0 +1,69 @@
import { getRoomData } from "./store";
import type { RoomStatus, MatchType } from "@/types";
export async function buildRoomStatus(
roomId: string,
): Promise<RoomStatus | null> {
const data = await getRoomData(roomId);
if (!data) return null;
const total = data.restaurants.length;
const allFinished =
data.users.length > 0 &&
data.users.every((u) => (data.swipeCounts[u] ?? 0) >= total);
let match = data.match;
let matchType: MatchType = null;
let matchLikes = 0;
let runnerUps: { id: string; likes: number }[] = [];
if (match) {
matchType = "unanimous";
matchLikes = data.users.length;
} else if (allFinished && data.restaurants.length > 0) {
const ranked = rankRestaurants(data.likes, data.restaurants);
if (ranked[0].likes > 0) {
match = ranked[0].id;
matchType = "best";
matchLikes = ranked[0].likes;
runnerUps = ranked.slice(1, 3).filter((r) => r.likes > 0);
} else {
match = ranked[0].id;
matchType = "no_match";
matchLikes = 0;
}
}
const likeCounts: Record<string, number> = {};
for (const [rid, users] of Object.entries(data.likes)) {
if (users.length > 0) {
likeCounts[rid] = users.length;
}
}
return {
roomId,
userCount: data.users.length,
match,
matchType,
matchLikes,
runnerUps,
likeCounts,
swipeCounts: data.swipeCounts,
restaurants: data.restaurants,
};
}
function rankRestaurants(
likes: Record<string, string[]>,
restaurants: { id: string; rating: number }[],
): { id: string; likes: number }[] {
return restaurants
.map((r) => ({
id: r.id,
likes: likes[r.id]?.length ?? 0,
rating: r.rating,
}))
.sort((a, b) => b.likes - a.likes || b.rating - a.rating)
.map(({ id, likes: l }) => ({ id, likes: l }));
}
+23
View File
@@ -0,0 +1,23 @@
type Listener = () => void;
const listeners = new Map<string, Set<Listener>>();
export function subscribe(roomId: string, listener: Listener): () => void {
if (!listeners.has(roomId)) {
listeners.set(roomId, new Set());
}
const set = listeners.get(roomId)!;
set.add(listener);
return () => {
set.delete(listener);
if (set.size === 0) listeners.delete(roomId);
};
}
export function notify(roomId: string) {
const set = listeners.get(roomId);
if (set) {
for (const fn of set) fn();
}
}