Skip to content

fix(android): add shared User-Agent to native OkHttp client#7372

Open
nkgotcode wants to merge 2 commits into
RocketChat:developfrom
nkgotcode:develop
Open

fix(android): add shared User-Agent to native OkHttp client#7372
nkgotcode wants to merge 2 commits into
RocketChat:developfrom
nkgotcode:develop

Conversation

@nkgotcode
Copy link
Copy Markdown

@nkgotcode nkgotcode commented Jun 1, 2026

Summary

  • add a shared Android native UserAgent helper for the Rocket.Chat mobile UA format
  • inject the default User-Agent in SSLPinningTurboModule.getSharedOkHttpClient() when a request does not already define one
  • keep explicit User-Agent headers untouched and reuse the shared helper from NotificationHelper
  • add regression coverage for default injection and explicit-header preservation

Test Plan

  • ./gradlew :app:testDebugUnitTest --tests "chat.rocket.reactnative.networking.SSLPinningTurboModuleTest"
  • ./gradlew :app:testDebugUnitTest --tests "chat.rocket.reactnative.notification.NotificationHelperTest"

Closes #7342

Summary by CodeRabbit

  • Bug Fixes

    • More reliable network connections via improved HTTP client caching and SSL behavior (falls back to non-pinning client when appropriate).
    • Consistent app identification in HTTP requests via a standardized User-Agent.
  • Improvements

    • VoIP and call flows now consistently use the shared HTTP client, preserving connection/ping behavior.
    • Notification handling continues to fetch avatars with the same timeouts and fallbacks.
  • Tests

    • Added tests verifying User-Agent injection and shared client behavior.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Jun 1, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

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: e5d9473f-e945-4d09-bb40-79969f7b25ad

📥 Commits

Reviewing files that changed from the base of the PR and between a538790 and 8ba83dd.

📒 Files selected for processing (4)
  • android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
  • android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt
  • android/app/src/main/java/chat/rocket/reactnative/voip/MediaCallsAnswerRequest.kt
  • android/app/src/test/java/chat/rocket/reactnative/voip/DDPClientTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
📜 Recent review details
🔇 Additional comments (3)
android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt (1)

23-26: LGTM!

Also applies to: 354-356

android/app/src/main/java/chat/rocket/reactnative/voip/MediaCallsAnswerRequest.kt (1)

46-52: LGTM!

android/app/src/test/java/chat/rocket/reactnative/voip/DDPClientTest.kt (1)

31-36: LGTM!


Walkthrough

Centralizes User-Agent generation in a new UserAgent class, adds a User-Agent-injecting interceptor to the shared OkHttp client (SSL pinning applied only when an alias is set), updates NotificationHelper to use UserAgent.get(), and adds tests plus VoIP client updates to consume the shared client.

Changes

User-Agent Interceptor Implementation

Layer / File(s) Summary
User-Agent contract
android/app/src/main/java/chat/rocket/reactnative/networking/UserAgent.java
New UserAgent class with public static String get() that builds RC Mobile; android <systemVersion>; v<appVersion> (<buildNumber>).
Shared OkHttp client interceptor integration
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
getSharedOkHttpClient() now builds a base client (timeouts, cookie jar) with a request interceptor that injects User-Agent when missing, keys cached client by certificate alias, applies SSL pinning only if alias != null, and getOkHttpClient() delegates to the shared client.
NotificationHelper User-Agent migration
android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java
Replaces inline User-Agent assembly with a call to UserAgent.get() and updates imports accordingly.
Interceptor behavior tests
android/app/src/test/java/chat/rocket/reactnative/networking/SSLPinningTurboModuleTest.kt
Robolectric/MockWebServer tests asserting default injection of RC Mobile User-Agent and preservation of an explicit User-Agent header.
VoIP clients and tests using shared client
android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt, android/app/src/main/java/chat/rocket/reactnative/voip/MediaCallsAnswerRequest.kt, android/app/src/test/java/chat/rocket/reactnative/voip/DDPClientTest.kt
DDPClient and MediaCallsAnswerRequest now derive builders from SSLPinningTurboModule.getSharedOkHttpClient() (no nullable fallback); DDPClient adds testPingIntervalMillis() and a test asserts the 30s websocket ping interval.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a shared User-Agent to the native OkHttp client via a new interceptor in SSLPinningTurboModule.
Linked Issues check ✅ Passed All coding requirements from issue #7342 are met: User-Agent interceptor added to SSLPinningTurboModule [#7342], centralized UserAgent helper created [#7342], explicit headers preserved [#7342], unit tests verify injection and preservation [#7342], and shared client usage simplified across sites [#7342].
Out of Scope Changes check ✅ Passed All changes directly support the User-Agent interceptor feature; no unrelated modifications detected. Changes include UserAgent helper, SSLPinningTurboModule refactor, test coverage, and simplification of shared client consumers.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PMD (7.25.0)
android/app/src/main/java/chat/rocket/reactnative/voip/DDPClient.kt

No java executable found in PATH


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java (1)

98-118: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove dead fallback logic in getOkHttpClient()/Elvis callers (and restore DDP pingInterval behavior)

  • SSLPinningTurboModule.getSharedOkHttpClient() always returns a non-null cached OkHttpClient, so getOkHttpClient()’s if (shared != null) check and the fallback OkHttpClient.Builder block are unreachable.
  • Downstream Kotlin Elvis fallbacks are also dead:
    • MediaCallsAnswerRequest.kt: getSharedOkHttpClient() ?: OkHttpClient() → the OkHttpClient() branch can be removed.
    • DDPClient.kt: getSharedOkHttpClient() ?: OkHttpClient.Builder().pingInterval(30, ...) → the pingInterval(30s) branch is never taken. If periodic WebSocket pings are needed, build from the shared client instead (e.g., getSharedOkHttpClient().newBuilder().pingInterval(30, TimeUnit.SECONDS).build()).
♻️ Proposed simplification of getOkHttpClient()
     protected OkHttpClient getOkHttpClient() {
-        OkHttpClient shared = getSharedOkHttpClient();
-        if (shared != null) {
-            return shared;
-        }
-        OkHttpClient.Builder builder = new OkHttpClient.Builder()
-                .connectTimeout(0, TimeUnit.MILLISECONDS)
-                .readTimeout(0, TimeUnit.MILLISECONDS)
-                .writeTimeout(0, TimeUnit.MILLISECONDS)
-                .cookieJar(new ReactCookieJarContainer());
-
-        if (alias != null) {
-            SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
-            X509TrustManager trustManager = getTrustManagerFactory();
-            if (sslSocketFactory != null) {
-                builder.sslSocketFactory(sslSocketFactory, trustManager);
-            }
-        }
-
-        return builder.build();
+        return getSharedOkHttpClient();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java`
around lines 98 - 118, The getOkHttpClient() method contains dead fallback
builder logic because getSharedOkHttpClient() always returns a non-null cached
OkHttpClient; remove the unreachable if (shared != null) check and the entire
fallback OkHttpClient.Builder block in SSLPinningTurboModule.getOkHttpClient(),
returning the shared client directly. Update callers that use Elvis fallbacks:
in MediaCallsAnswerRequest.kt remove the "?: OkHttpClient()" fallback and just
use getSharedOkHttpClient(), and in DDPClient.kt replace the Elvis branch that
creates a new OkHttpClient.Builder().pingInterval(30, ...) with building off the
shared client (e.g., getSharedOkHttpClient().newBuilder().pingInterval(30,
TimeUnit.SECONDS).build()) so pingInterval behavior is restored while still
using the cached client.
🧹 Nitpick comments (1)
android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java (1)

42-48: 💤 Low value

Guard shared-client state against concurrent access.

sharedClient, sharedClientAlias, and alias are static mutable fields read/written by getSharedOkHttpClient() without synchronization. Callers like DDPClient and MediaCallsAnswerRequest initialize their clients from lazy initializers that can run on different threads, so the check-then-build sequence here is a TOCTOU race. The worst case is benign (an extra client gets built), but a synchronized method (or volatile + double-checked locking) makes the cache reliable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java`
around lines 42 - 48, The static cache check in getSharedOkHttpClient() is racy
because sharedClient, sharedClientAlias, and alias are mutable statics accessed
from multiple threads; make access thread-safe by serializing reads/writes
(e.g., declare getSharedOkHttpClient() synchronized or use a volatile
sharedClient plus a synchronized block with double-checked locking) so the
check-then-build sequence cannot race—update the logic around
sharedClient/sharedClientAlias/alias in getSharedOkHttpClient() accordingly to
ensure only one client is published for a given alias.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java`:
- Around line 98-118: The getOkHttpClient() method contains dead fallback
builder logic because getSharedOkHttpClient() always returns a non-null cached
OkHttpClient; remove the unreachable if (shared != null) check and the entire
fallback OkHttpClient.Builder block in SSLPinningTurboModule.getOkHttpClient(),
returning the shared client directly. Update callers that use Elvis fallbacks:
in MediaCallsAnswerRequest.kt remove the "?: OkHttpClient()" fallback and just
use getSharedOkHttpClient(), and in DDPClient.kt replace the Elvis branch that
creates a new OkHttpClient.Builder().pingInterval(30, ...) with building off the
shared client (e.g., getSharedOkHttpClient().newBuilder().pingInterval(30,
TimeUnit.SECONDS).build()) so pingInterval behavior is restored while still
using the cached client.

---

Nitpick comments:
In
`@android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java`:
- Around line 42-48: The static cache check in getSharedOkHttpClient() is racy
because sharedClient, sharedClientAlias, and alias are mutable statics accessed
from multiple threads; make access thread-safe by serializing reads/writes
(e.g., declare getSharedOkHttpClient() synchronized or use a volatile
sharedClient plus a synchronized block with double-checked locking) so the
check-then-build sequence cannot race—update the logic around
sharedClient/sharedClientAlias/alias in getSharedOkHttpClient() accordingly to
ensure only one client is published for a given alias.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3b41eff-0edb-4b5c-8bee-2611f885e1f6

📥 Commits

Reviewing files that changed from the base of the PR and between ff5ec0c and a538790.

📒 Files selected for processing (4)
  • android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java
  • android/app/src/main/java/chat/rocket/reactnative/networking/UserAgent.java
  • android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java
  • android/app/src/test/java/chat/rocket/reactnative/networking/SSLPinningTurboModuleTest.kt
📜 Review details
🔇 Additional comments (4)
android/app/src/main/java/chat/rocket/reactnative/networking/UserAgent.java (1)

8-15: LGTM!

android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningTurboModule.java (1)

47-65: User-Agent interceptor logic is correct.

The application-level interceptor injects UserAgent.get() only when no User-Agent header is present, preserving explicit overrides. Caching keyed by alias is sound.

android/app/src/main/java/chat/rocket/reactnative/notification/NotificationHelper.java (1)

49-51: LGTM!

android/app/src/test/java/chat/rocket/reactnative/networking/SSLPinningTurboModuleTest.kt (1)

32-68: LGTM!

- remove dead null fallbacks around shared OkHttp client access
- restore DDP websocket pingInterval on top of the shared client
- synchronize shared client cache publication
- add DDP regression for preserved ping interval
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.

Android: add User-Agent interceptor to shared OkHttp client

2 participants