From 3c23788ff71178e46a68be33947f65773e16f6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Mon, 18 May 2026 23:00:54 +0800 Subject: [PATCH] fix: preserve notification plugin listener context The notification plugin can return an object whose unregister method expects its original listener object as this. Calling the extracted method directly drops that context and produces an unhandled rejection when the browser shell cleans up listeners. Constraint: The desktop frontend also runs in browser-like dev shells where Tauri plugin APIs may be partially present Rejected: Ignore cleanup errors | it leaves noisy unhandled rejections during local UI smoke tests Confidence: high Scope-risk: narrow Tested: cd desktop && bun run test -- desktopNotifications Not-tested: Native notification click behavior on every desktop platform --- desktop/src/lib/desktopNotifications.test.ts | 10 +++++++--- desktop/src/lib/desktopNotifications.ts | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/desktop/src/lib/desktopNotifications.test.ts b/desktop/src/lib/desktopNotifications.test.ts index 69783946..e9deb983 100644 --- a/desktop/src/lib/desktopNotifications.test.ts +++ b/desktop/src/lib/desktopNotifications.test.ts @@ -344,7 +344,11 @@ describe('desktopNotifications', () => { it('forwards notification click targets from native and plugin listeners', async () => { const unlistenNative = vi.fn() - const unregisterPlugin = vi.fn() + const pluginListener = { + unregister: vi.fn(function (this: unknown) { + expect(this).toBe(pluginListener) + }), + } type NativeClickCallback = (event: { payload: unknown }) => void type PluginClickCallback = (notification: unknown) => void let nativeCallback: NativeClickCallback = () => { @@ -366,7 +370,7 @@ describe('desktopNotifications', () => { notificationPluginMock.onAction.mockImplementation(async (callback: (notification: unknown) => void) => { pluginCallback = callback pluginRegistered = true - return { unregister: unregisterPlugin } + return pluginListener }) const onTarget = vi.fn() @@ -382,7 +386,7 @@ describe('desktopNotifications', () => { cleanup() expect(unlistenNative).toHaveBeenCalledTimes(1) - expect(unregisterPlugin).toHaveBeenCalledTimes(1) + expect(pluginListener.unregister).toHaveBeenCalledTimes(1) }) it('requests OS-level window attention for blocking prompts', async () => { diff --git a/desktop/src/lib/desktopNotifications.ts b/desktop/src/lib/desktopNotifications.ts index 26450d76..d39f52d4 100644 --- a/desktop/src/lib/desktopNotifications.ts +++ b/desktop/src/lib/desktopNotifications.ts @@ -339,7 +339,7 @@ function disposePluginListener(listener: unknown): void { const unregister = listener && typeof listener === 'object' ? (listener as { unregister?: () => Promise | void }).unregister : undefined - if (unregister) void unregister() + if (typeof unregister === 'function') void unregister.call(listener) } export async function installDesktopNotificationClickListener(