Throttle realtime refresh bursts
This commit is contained in:
@@ -4,6 +4,13 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import clsx from "clsx";
|
||||
import type { BossEventName } from "@/lib/boss-events";
|
||||
import {
|
||||
cancelScheduledRefresh,
|
||||
createRefreshThrottleState,
|
||||
markScheduledRefreshExecuted,
|
||||
planThrottledRefresh,
|
||||
shouldRefreshRealtimeEvent,
|
||||
} from "@/lib/realtime-refresh";
|
||||
import type { SkillInventoryDeviceGroup } from "@/lib/boss-projections";
|
||||
import {
|
||||
clearNativeSessionSnapshot,
|
||||
@@ -263,63 +270,43 @@ export function RealtimeRefresh({
|
||||
conversationUpdatedNotes?: string[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const throttleStateRef = useRef(createRefreshThrottleState());
|
||||
const pendingTimerRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const source = new EventSource("/api/v1/events");
|
||||
const projectScopeIds = new Set(
|
||||
[projectId, ...(projectIds ?? [])]
|
||||
.filter((value): value is string => Boolean(value?.trim()))
|
||||
.map((value) => value.trim()),
|
||||
);
|
||||
const throttleState = throttleStateRef.current;
|
||||
const listeners = Array.from(new Set([
|
||||
"conversation.context_indicator.updated",
|
||||
"project.context_risk.updated",
|
||||
...events,
|
||||
]));
|
||||
const shouldRefresh = (event: Event) => {
|
||||
let payload: { projectId?: string; note?: string } | null = null;
|
||||
const eventData = "data" in event && typeof event.data === "string" ? event.data : "";
|
||||
const hasPayloadData = Boolean(eventData.trim());
|
||||
|
||||
if (hasPayloadData) {
|
||||
try {
|
||||
const parsed = JSON.parse(eventData) as { projectId?: string; note?: string };
|
||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||
payload = parsed;
|
||||
}
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (projectScopeIds.size > 0) {
|
||||
if (!payload || typeof payload.projectId !== "string" || !payload.projectId.trim()) {
|
||||
return true;
|
||||
}
|
||||
if (!projectScopeIds.has(payload.projectId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === "conversation.updated" && conversationUpdatedNotes?.length) {
|
||||
if (!payload || typeof payload.note !== "string" || !payload.note.trim()) {
|
||||
return false;
|
||||
}
|
||||
if ((payload.note) && !conversationUpdatedNotes.includes(payload.note)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
const listenerMap = new Map<string, (event: Event) => void>();
|
||||
|
||||
for (const event of listeners) {
|
||||
const refresh = (nextEvent: Event) => {
|
||||
if (!shouldRefresh(nextEvent)) {
|
||||
const eventData = "data" in nextEvent ? nextEvent.data : undefined;
|
||||
if (!shouldRefreshRealtimeEvent({
|
||||
eventType: nextEvent.type,
|
||||
eventData,
|
||||
projectId,
|
||||
projectIds,
|
||||
conversationUpdatedNotes,
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
router.refresh();
|
||||
const decision = planThrottledRefresh(throttleState, Date.now(), 400);
|
||||
if (decision.type === "refresh_now") {
|
||||
router.refresh();
|
||||
return;
|
||||
}
|
||||
if (decision.type === "schedule_refresh" && pendingTimerRef.current === null) {
|
||||
pendingTimerRef.current = window.setTimeout(() => {
|
||||
pendingTimerRef.current = null;
|
||||
markScheduledRefreshExecuted(throttleState, Date.now());
|
||||
router.refresh();
|
||||
}, decision.delayMs);
|
||||
}
|
||||
};
|
||||
listenerMap.set(event, refresh);
|
||||
source.addEventListener(event, refresh);
|
||||
@@ -332,6 +319,11 @@ export function RealtimeRefresh({
|
||||
source.removeEventListener(event, refresh);
|
||||
}
|
||||
}
|
||||
if (pendingTimerRef.current !== null) {
|
||||
window.clearTimeout(pendingTimerRef.current);
|
||||
pendingTimerRef.current = null;
|
||||
}
|
||||
cancelScheduledRefresh(throttleState);
|
||||
source.close();
|
||||
};
|
||||
}, [conversationUpdatedNotes, events, projectId, projectIds, router]);
|
||||
|
||||
Reference in New Issue
Block a user