From 4ba267d30c6acc38c30d36865aa9ef1343d986b2 Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 9 Dec 2024 13:40:10 +0100 Subject: [PATCH 1/2] Subscribe to Pusher only when it's initialized --- .../Navigation/AppNavigator/AuthScreens.tsx | 7 +- src/libs/Pusher/pusher.ts | 89 ++++++++++--------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index be3139e0d65b..7d897d485a78 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -241,7 +241,6 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const {toggleSearch} = useSearchRouterContext(); const modal = useRef({}); - const [didPusherInit, setDidPusherInit] = useState(false); const {isOnboardingCompleted} = useOnboardingFlowRouter(); const [initialReportID] = useState(() => { const currentURL = getCurrentUrl(); @@ -280,9 +279,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie NetworkConnection.listenForReconnect(); NetworkConnection.onReconnect(handleNetworkReconnect); PusherConnectionManager.init(); - initializePusher().then(() => { - setDidPusherInit(true); - }); + initializePusher(); // In Hybrid App we decide to call one of those method when booting ND and we don't want to duplicate calls if (!NativeModules.HybridAppModule) { @@ -591,7 +588,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie - {didPusherInit && } + ); } diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts index 1a511eec391c..c5bda63b8dba 100644 --- a/src/libs/Pusher/pusher.ts +++ b/src/libs/Pusher/pusher.ts @@ -74,6 +74,8 @@ let pusherSocketID = ''; const socketEventCallbacks: SocketEventCallback[] = []; let customAuthorizer: ChannelAuthorizerGenerator; +let initPromise: Promise; + const eventsBoundToChannels = new Map>(); /** @@ -88,7 +90,7 @@ function callSocketEventCallbacks(eventName: SocketEventName, data?: EventCallba * @returns resolves when Pusher has connected */ function init(args: Args, params?: unknown): Promise { - return new Promise((resolve) => { + initPromise = new Promise((resolve) => { if (socket) { resolve(); return; @@ -140,6 +142,8 @@ function init(args: Args, params?: unknown): Promise { callSocketEventCallbacks('state_change', states); }); }); + + return initPromise; } /** @@ -236,52 +240,55 @@ function subscribe( eventCallback: (data: EventData) => void = () => {}, onResubscribe = () => {}, ): Promise { - return new Promise((resolve, reject) => { - InteractionManager.runAfterInteractions(() => { - // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. - if (!socket) { - throw new Error(`[Pusher] instance not found. Pusher.subscribe() + return initPromise.then( + () => + new Promise((resolve, reject) => { + InteractionManager.runAfterInteractions(() => { + // We cannot call subscribe() before init(). Prevent any attempt to do this on dev. + if (!socket) { + throw new Error(`[Pusher] instance not found. Pusher.subscribe() most likely has been called before Pusher.init()`); - } - - Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName}); - let channel = getChannel(channelName); + } - if (!channel?.subscribed) { - channel = socket.subscribe(channelName); - let isBound = false; - channel.bind('pusher:subscription_succeeded', () => { - // Check so that we do not bind another event with each reconnect attempt - if (!isBound) { + Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName}); + let channel = getChannel(channelName); + + if (!channel?.subscribed) { + channel = socket.subscribe(channelName); + let isBound = false; + channel.bind('pusher:subscription_succeeded', () => { + // Check so that we do not bind another event with each reconnect attempt + if (!isBound) { + bindEventToChannel(channel, eventName, eventCallback); + resolve(); + isBound = true; + return; + } + + // When subscribing for the first time we register a success callback that can be + // called multiple times when the subscription succeeds again in the future + // e.g. as a result of Pusher disconnecting and reconnecting. This callback does + // not fire on the first subscription_succeeded event. + onResubscribe(); + }); + + channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => { + const {type, error, status} = data; + Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { + channelName, + status, + type, + error, + }); + reject(error); + }); + } else { bindEventToChannel(channel, eventName, eventCallback); resolve(); - isBound = true; - return; } - - // When subscribing for the first time we register a success callback that can be - // called multiple times when the subscription succeeds again in the future - // e.g. as a result of Pusher disconnecting and reconnecting. This callback does - // not fire on the first subscription_succeeded event. - onResubscribe(); }); - - channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => { - const {type, error, status} = data; - Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', { - channelName, - status, - type, - error, - }); - reject(error); - }); - } else { - bindEventToChannel(channel, eventName, eventCallback); - resolve(); - } - }); - }); + }), + ); } /** From f2090bfeb80407af458029b4c76bc74eb8da0dfb Mon Sep 17 00:00:00 2001 From: Jan Nowakowski Date: Mon, 9 Dec 2024 16:38:02 +0100 Subject: [PATCH 2/2] Fix tests --- tests/ui/UnreadIndicatorsTest.tsx | 6 +++++- tests/utils/PusherHelper.ts | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index e6acd3e9a19d..f7f4574b1d29 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -182,7 +182,11 @@ function signInAndGetAppWithUnreadChat(): Promise { } describe('Unread Indicators', () => { - afterEach(() => { + beforeAll(() => { + PusherHelper.setup(); + }); + + beforeEach(() => { jest.clearAllMocks(); Onyx.clear(); diff --git a/tests/utils/PusherHelper.ts b/tests/utils/PusherHelper.ts index 9f0f71baafd3..8547c25b1235 100644 --- a/tests/utils/PusherHelper.ts +++ b/tests/utils/PusherHelper.ts @@ -22,6 +22,8 @@ function setup() { cluster: CONFIG.PUSHER.CLUSTER, authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`, }); + + window.getPusherInstance()?.connection.emit('connected'); } function emitOnyxUpdate(args: OnyxServerUpdate[]) {