fix(ui): prevent async listener leaks on back navigation
This commit is contained in:
parent
2618c2ce77
commit
b6e31f9312
3 changed files with 123 additions and 78 deletions
|
|
@ -66,28 +66,41 @@ export default function NotificationCenter() {
|
|||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let unlisten: (() => void) | null = null;
|
||||
|
||||
const setup = async () => {
|
||||
unlisten = await listen<NewNotificationEvent>("new-notification", (event) => {
|
||||
const incoming = event.payload.notification;
|
||||
try {
|
||||
const cleanup = await listen<NewNotificationEvent>("new-notification", (event) => {
|
||||
const incoming = event.payload.notification;
|
||||
|
||||
if (projectId && incoming.project_id !== projectId) {
|
||||
if (projectId && incoming.project_id !== projectId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNotifications((prev) => {
|
||||
const withoutDuplicate = prev.filter((n) => n.id !== incoming.id);
|
||||
return [incoming, ...withoutDuplicate];
|
||||
});
|
||||
|
||||
void showSystemNotification(incoming);
|
||||
});
|
||||
|
||||
if (cancelled) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
setNotifications((prev) => {
|
||||
const withoutDuplicate = prev.filter((n) => n.id !== incoming.id);
|
||||
return [incoming, ...withoutDuplicate];
|
||||
});
|
||||
|
||||
void showSystemNotification(incoming);
|
||||
});
|
||||
unlisten = cleanup;
|
||||
} catch (error: unknown) {
|
||||
console.error("Failed to subscribe to notifications", error);
|
||||
}
|
||||
};
|
||||
|
||||
void setup();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,74 +160,93 @@ export default function ProjectLiveAgent() {
|
|||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
|
||||
let cancelled = false;
|
||||
let stop: (() => void) | null = null;
|
||||
|
||||
void (async () => {
|
||||
const [unlistenMessage, unlistenStarted, unlistenChunk, unlistenFinished, unlistenError] =
|
||||
await Promise.all([
|
||||
listen<LiveEventPayload>("live-agent-message", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
const setup = async () => {
|
||||
try {
|
||||
const [unlistenMessage, unlistenStarted, unlistenChunk, unlistenFinished, unlistenError] =
|
||||
await Promise.all([
|
||||
listen<LiveEventPayload>("live-agent-message", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
|
||||
setMessages((prev) => {
|
||||
const existingIndex = prev.findIndex((msg) => msg.id === payload.message.id);
|
||||
if (existingIndex === -1) {
|
||||
return [...prev, payload.message];
|
||||
setMessages((prev) => {
|
||||
const existingIndex = prev.findIndex((msg) => msg.id === payload.message.id);
|
||||
if (existingIndex === -1) {
|
||||
return [...prev, payload.message];
|
||||
}
|
||||
|
||||
const next = [...prev];
|
||||
next[existingIndex] = payload.message;
|
||||
return next;
|
||||
});
|
||||
|
||||
if (payload.message.sender === "agent" && payload.message.content.trim() !== "") {
|
||||
setStreamingAgentResponse(null);
|
||||
}
|
||||
|
||||
const next = [...prev];
|
||||
next[existingIndex] = payload.message;
|
||||
return next;
|
||||
});
|
||||
|
||||
if (payload.message.sender === "agent" && payload.message.content.trim() !== "") {
|
||||
}),
|
||||
listen<LiveStreamStatusPayload>("live-agent-stream-started", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse("");
|
||||
}),
|
||||
listen<LiveStreamChunkPayload>("live-agent-stream-chunk", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse((prev) => `${prev ?? ""}${payload.chunk}`);
|
||||
}),
|
||||
listen<LiveStreamStatusPayload>("live-agent-stream-finished", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse(null);
|
||||
}
|
||||
}),
|
||||
listen<LiveStreamStatusPayload>("live-agent-stream-started", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse("");
|
||||
}),
|
||||
listen<LiveStreamChunkPayload>("live-agent-stream-chunk", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse((prev) => `${prev ?? ""}${payload.chunk}`);
|
||||
}),
|
||||
listen<LiveStreamStatusPayload>("live-agent-stream-finished", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse(null);
|
||||
}),
|
||||
listen<LiveStreamStatusPayload>("live-agent-stream-error", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse(null);
|
||||
setMessages((prev) =>
|
||||
prev.filter((msg) => !(msg.sender === "agent" && msg.content.trim() === ""))
|
||||
);
|
||||
if (payload.error) {
|
||||
setError(payload.error);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}),
|
||||
listen<LiveStreamStatusPayload>("live-agent-stream-error", (event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.project_id !== projectId) return;
|
||||
if (payload.session_id !== selectedSessionId) return;
|
||||
setStreamingAgentResponse(null);
|
||||
setMessages((prev) =>
|
||||
prev.filter((msg) => !(msg.sender === "agent" && msg.content.trim() === ""))
|
||||
);
|
||||
if (payload.error) {
|
||||
setError(payload.error);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
stop = () => {
|
||||
unlistenMessage();
|
||||
unlistenStarted();
|
||||
unlistenChunk();
|
||||
unlistenFinished();
|
||||
unlistenError();
|
||||
};
|
||||
})();
|
||||
const cleanup = () => {
|
||||
unlistenMessage();
|
||||
unlistenStarted();
|
||||
unlistenChunk();
|
||||
unlistenFinished();
|
||||
unlistenError();
|
||||
};
|
||||
|
||||
if (cancelled) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
stop = cleanup;
|
||||
} catch (err: unknown) {
|
||||
if (!cancelled) {
|
||||
setError(getErrorMessage(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void setup();
|
||||
|
||||
return () => {
|
||||
if (stop) stop();
|
||||
cancelled = true;
|
||||
if (stop) {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
}, [projectId, selectedSessionId]);
|
||||
|
||||
|
|
|
|||
|
|
@ -76,18 +76,31 @@ export default function ProjectTasks() {
|
|||
|
||||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
let stop: (() => void) | null = null;
|
||||
let cancelled = false;
|
||||
let unlisten: (() => void) | null = null;
|
||||
|
||||
void (async () => {
|
||||
const unlisten = await listen<TaskEventPayload>("agent-task-updated", (event) => {
|
||||
if (event.payload.project_id !== projectId) return;
|
||||
void refresh();
|
||||
void listen<TaskEventPayload>("agent-task-updated", (event) => {
|
||||
if (event.payload.project_id !== projectId) return;
|
||||
void refresh();
|
||||
})
|
||||
.then((cleanup) => {
|
||||
if (cancelled) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
unlisten = cleanup;
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!cancelled) {
|
||||
setError(getErrorMessage(err));
|
||||
}
|
||||
});
|
||||
stop = unlisten;
|
||||
})();
|
||||
|
||||
return () => {
|
||||
if (stop) stop();
|
||||
cancelled = true;
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
}
|
||||
};
|
||||
}, [projectId]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue