From be2dfe4bac3f9c694cacbf8b5bb76597f03cb61e Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 20:17:45 -0700 Subject: [PATCH] fix: drop mobile notification stream on cleanup (#2887) --- .../mobile-notifications.test.ts | 36 +++++++++++++++++++ .../src/notifications/mobile-notifications.ts | 4 ++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 mobile/src/notifications/mobile-notifications.test.ts diff --git a/mobile/src/notifications/mobile-notifications.test.ts b/mobile/src/notifications/mobile-notifications.test.ts new file mode 100644 index 000000000..32893cc30 --- /dev/null +++ b/mobile/src/notifications/mobile-notifications.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it, vi } from 'vitest' +import { subscribeToDesktopNotifications } from './mobile-notifications' +import type { RpcClient } from '../transport/rpc-client' + +vi.mock('expo-notifications', () => ({ + AndroidImportance: { HIGH: 'high' }, + setNotificationChannelAsync: vi.fn(), + getPermissionsAsync: vi.fn(), + requestPermissionsAsync: vi.fn(), + scheduleNotificationAsync: vi.fn() +})) + +vi.mock('react-native', () => ({ + Platform: { OS: 'ios' } +})) + +vi.mock('../storage/preferences', () => ({ + loadPushNotificationsEnabled: vi.fn() +})) + +describe('subscribeToDesktopNotifications', () => { + it('drops the local stream when disposed before the desktop returns ready', () => { + const unsubscribeStream = vi.fn() + const client = { + subscribe: vi.fn(() => unsubscribeStream), + getState: vi.fn(() => 'connected'), + sendRequest: vi.fn() + } as unknown as RpcClient + + const unsubscribe = subscribeToDesktopNotifications(client, 'host-1') + unsubscribe() + + expect(unsubscribeStream).toHaveBeenCalledTimes(1) + expect(client.sendRequest).not.toHaveBeenCalled() + }) +}) diff --git a/mobile/src/notifications/mobile-notifications.ts b/mobile/src/notifications/mobile-notifications.ts index f22b459a6..a26aa1dc5 100644 --- a/mobile/src/notifications/mobile-notifications.ts +++ b/mobile/src/notifications/mobile-notifications.ts @@ -114,8 +114,10 @@ export function subscribeToDesktopNotifications(client: RpcClient, hostId: strin // unmount races with disconnect). sendRequest rejects immediately on a // closed client — swallow it since server-side cleanup happens via // connection-close anyway. + // Always drop the local stream first; readiness can race unmount and we + // must not retain the callback while waiting for a subscription id. + unsubscribeStream() if (subscriptionId) { - unsubscribeStream() unsubscribeServer(subscriptionId) } }