fix(ui): prevent async listener leaks on back navigation

This commit is contained in:
thibaud-lclr 2026-04-21 10:33:12 +02:00
parent 2618c2ce77
commit b6e31f9312
3 changed files with 123 additions and 78 deletions

View file

@ -66,10 +66,12 @@ export default function NotificationCenter() {
}, [projectId]);
useEffect(() => {
let cancelled = false;
let unlisten: (() => void) | null = null;
const setup = async () => {
unlisten = await listen<NewNotificationEvent>("new-notification", (event) => {
try {
const cleanup = await listen<NewNotificationEvent>("new-notification", (event) => {
const incoming = event.payload.notification;
if (projectId && incoming.project_id !== projectId) {
@ -83,11 +85,22 @@ export default function NotificationCenter() {
void showSystemNotification(incoming);
});
if (cancelled) {
cleanup();
return;
}
unlisten = cleanup;
} catch (error: unknown) {
console.error("Failed to subscribe to notifications", error);
}
};
void setup();
return () => {
cancelled = true;
if (unlisten) {
unlisten();
}

View file

@ -160,9 +160,11 @@ export default function ProjectLiveAgent() {
useEffect(() => {
if (!projectId) return;
let cancelled = false;
let stop: (() => void) | null = null;
void (async () => {
const setup = async () => {
try {
const [unlistenMessage, unlistenStarted, unlistenChunk, unlistenFinished, unlistenError] =
await Promise.all([
listen<LiveEventPayload>("live-agent-message", (event) => {
@ -217,17 +219,34 @@ export default function ProjectLiveAgent() {
}),
]);
stop = () => {
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]);

View file

@ -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) => {
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]);