Skip to content

fix: re-subscribe rooms stream after forced socket reopen#7362

Open
diegolmello wants to merge 1 commit into
developfrom
rooms-freeze-android
Open

fix: re-subscribe rooms stream after forced socket reopen#7362
diegolmello wants to merge 1 commit into
developfrom
rooms-freeze-android

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented May 29, 2026

Proposed changes

After the app sits in the background for a while the DDP socket goes stale, so foregrounding triggers checkAndReopenforceReopen. forceReopen drops every SDK subscription and reopens the socket without going through connect(), so the module-level roomsSubscription guard in subscribeRooms stays set. The next subscribeRooms() therefore short-circuits and never re-subscribes stream-notify-user, and the rooms list silently stops reflecting subscription/favorite/read changes until a manual reconnect or app restart.

This resets that guard from the socket 'close' listener (via unsubscribeRooms()) — the same teardown connect() already performs at startup — so the resume-login that follows the reopen re-subscribes stream-notify-user. stream-notify-user is the only stream behind that guard; the other streams (permissions, presence, settings, roles) call sdk.subscribe(...) unconditionally and were never affected.

Issue(s)

https://rocketchat.atlassian.net/browse/SUP-1047

How to test or reproduce

  1. Log in and open the rooms list.
  2. Background the app and cut connectivity (airplane mode) for ~60s so the DDP socket goes stale.
  3. Restore connectivity and foreground the app.
  4. From another client (or REST), trigger a subscriptions-changed event — e.g. favorite/unfavorite a room or mark one read.
  • Before: the rooms list does not update; the change only appears after a manual reconnect / app restart.
  • After: the rooms list updates in real time again.

Verified on an Android 16 emulator: after foregrounding, subscribeRooms() re-subscribes stream-notify-user and a favorite toggle then fires the handler and the WatermelonDB write. A regression test in connect.test.ts asserts the 'close' listener calls unsubscribeRooms() (and fails if the call is removed).

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

The fix lives in the 'close' listener rather than in subscribeRooms itself because 'close' is already the place app state is reset for reconnect (it dispatches disconnectAction() to flip Redux meteor.connected → false). The SDK's forceReopen documents this contract: it emits 'close' so connect.ts flips that state, otherwise the next 'connected' short-circuits and the loginRequest → subscribeNotifyUser chain never re-runs. The roomsSubscription guard is just a second piece of "am I subscribed?" state that has to be reset in that same spot.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed connection recovery to properly reset subscriptions when VoIP/SDK connections close and reopen, ensuring subscriptions are re-established correctly after connection cycles.
  • Tests

    • Added regression test to verify proper cleanup behavior during connection close events.

Review Change Stack

A long background marks the DDP socket stale, so foregrounding triggers
checkAndReopen -> forceReopen, which drops all SDK subscriptions and reopens
the socket without going through connect(). The module-level roomsSubscription
guard therefore stayed set, so subscribeRooms() skipped re-subscribing
stream-notify-user and the rooms list silently stopped reflecting
subscriptions/favorites/reads until a manual reconnect.

Reset the guard from the socket 'close' listener (via unsubscribeRooms), the
same teardown connect() already performs, so the resume-login that follows the
reopen re-subscribes stream-notify-user. Other streams (permissions, presence,
settings, roles) subscribe unconditionally and were never affected.
@diegolmello diegolmello requested a deployment to approve_e2e_testing May 29, 2026 20:52 — with GitHub Actions Waiting
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b9bb4d5-1c7e-4f2e-b316-d9fd777e9db2

📥 Commits

Reviewing files that changed from the base of the PR and between 815ff6c and 19944a4.

📒 Files selected for processing (2)
  • app/lib/services/connect.test.ts
  • app/lib/services/connect.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/services/connect.test.ts
  • app/lib/services/connect.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Use TypeScript with strict mode and baseUrl set to app/ for import resolution

Files:

  • app/lib/services/connect.test.ts
  • app/lib/services/connect.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use Prettier with tabs, single quotes, 130 char width, no trailing commas, arrow parens avoid, bracket same line
Use @rocket.chat/eslint-config base with React, React Native, TypeScript, Jest plugins

Files:

  • app/lib/services/connect.test.ts
  • app/lib/services/connect.ts
app/lib/services/connect.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Server connection management should be in app/lib/services/connect.ts

Files:

  • app/lib/services/connect.ts
🧠 Learnings (1)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/lib/services/connect.test.ts
  • app/lib/services/connect.ts
🔇 Additional comments (2)
app/lib/services/connect.ts (1)

135-139: LGTM!

app/lib/services/connect.test.ts (1)

4-4: LGTM!

Also applies to: 485-513


Walkthrough

The PR adds a call to unsubscribeRooms() in the socket 'close' event handler to reset the subscription guard when the connection closes and reopens. A regression test verifies this cleanup occurs when the stream emits a close event.

Changes

Stream Close Unsubscribe Rooms Guard Reset

Layer / File(s) Summary
Close handler cleanup
app/lib/services/connect.ts
The 'close' event handler now calls unsubscribeRooms() to reset the rooms-subscription guard, with explanatory comments documenting that this reset is required when checkAndReopen wipes SDK subscriptions without re-running connect().
Regression test for close-event cleanup
app/lib/services/connect.test.ts
New import of unsubscribeRooms and regression test that verifies unsubscribeRooms() is called exactly once when the stream 'close' event fires after connect(), with proper mock state management and test setup/teardown.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: re-subscribing the rooms stream after a forced socket reopen, which directly addresses the bug fixed in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • SUP-1047: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.73.0.109000

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant