From fe1858f0e6a2c4f8f2af948c9b8c281b44a57aaf Mon Sep 17 00:00:00 2001 From: Estrella Pan Date: Tue, 24 Feb 2026 19:44:49 +0100 Subject: [PATCH] fix(webui): close EventSource and clear timers on component unmount Prevent memory leaks by ensuring the search EventSource connection is closed when the modal unmounts and setTimeout handles are cleared in copy-to-clipboard flows across modal components. Co-Authored-By: Claude Opus 4.6 --- webui/src/components/ab-add-rss.vue | 9 ++++++++- webui/src/components/ab-edit-rule.vue | 9 ++++++++- webui/src/components/search/ab-search-confirm.vue | 9 ++++++++- webui/src/components/search/ab-search-modal.vue | 6 ++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/webui/src/components/ab-add-rss.vue b/webui/src/components/ab-add-rss.vue index 8a7a2e54..65280152 100644 --- a/webui/src/components/ab-add-rss.vue +++ b/webui/src/components/ab-add-rss.vue @@ -147,17 +147,24 @@ function goBack() { step.value = 'input'; } +let copyTimer: ReturnType | undefined; + async function copyRssLink() { const rssLink = rule.value.rss_link?.[0] || rss.value.url || ''; if (rssLink) { await navigator.clipboard.writeText(rssLink); copied.value = true; - setTimeout(() => { + clearTimeout(copyTimer); + copyTimer = setTimeout(() => { copied.value = false; }, 2000); } } +onBeforeUnmount(() => { + clearTimeout(copyTimer); +}); + async function autoDetectOffset() { if (!rule.value.id) return; offsetLoading.value = true; diff --git a/webui/src/components/ab-edit-rule.vue b/webui/src/components/ab-edit-rule.vue index 1cd51e77..ce480428 100644 --- a/webui/src/components/ab-edit-rule.vue +++ b/webui/src/components/ab-edit-rule.vue @@ -82,17 +82,24 @@ const infoTags = computed(() => { }); // Copy RSS link +let copyTimer: ReturnType | undefined; + async function copyRssLink() { const rssLink = localRule.value.rss_link?.[0] || ''; if (rssLink) { await navigator.clipboard.writeText(rssLink); copied.value = true; - setTimeout(() => { + clearTimeout(copyTimer); + copyTimer = setTimeout(() => { copied.value = false; }, 2000); } } +onBeforeUnmount(() => { + clearTimeout(copyTimer); +}); + // Auto detect offset using the new detectOffset API async function autoDetectOffset() { if (!localRule.value.official_title || !localRule.value.season) return; diff --git a/webui/src/components/search/ab-search-confirm.vue b/webui/src/components/search/ab-search-confirm.vue index d774c7b3..9ad82a22 100644 --- a/webui/src/components/search/ab-search-confirm.vue +++ b/webui/src/components/search/ab-search-confirm.vue @@ -103,17 +103,24 @@ const infoTags = computed(() => { }); // Copy RSS link +let copyTimer: ReturnType | undefined; + async function copyRssLink() { const rssLink = localBangumi.value.rss_link?.[0] || ''; if (rssLink) { await navigator.clipboard.writeText(rssLink); copied.value = true; - setTimeout(() => { + clearTimeout(copyTimer); + copyTimer = setTimeout(() => { copied.value = false; }, 2000); } } +onBeforeUnmount(() => { + clearTimeout(copyTimer); +}); + // Auto detect offset async function autoDetectOffset() { if (!localBangumi.value.id) return; diff --git a/webui/src/components/search/ab-search-modal.vue b/webui/src/components/search/ab-search-modal.vue index 09227703..9018403f 100644 --- a/webui/src/components/search/ab-search-modal.vue +++ b/webui/src/components/search/ab-search-modal.vue @@ -27,6 +27,7 @@ const { getProviders, onSearch, clearSearch, + closeSearch, selectResult, clearSelectedResult, } = useSearchStore(); @@ -61,6 +62,11 @@ const expandedCategories = ref>(new Set()); +// Close EventSource on unmount (prevents leak if navigating away mid-search) +onBeforeUnmount(() => { + closeSearch(); +}); + // Close on Escape onKeyStroke('Escape', () => { if (selectedResult.value) {