Skip to content

Bump @pusher/pusher-websocket-react-native to 1.3.5 for the zombie-connection and stuck-reconnect fixes - #98052

Open
adhorodyski wants to merge 2 commits into
Expensify:mainfrom
callstack-internal:2797-bump-pusher-rebased
Open

Bump @pusher/pusher-websocket-react-native to 1.3.5 for the zombie-connection and stuck-reconnect fixes#98052
adhorodyski wants to merge 2 commits into
Expensify:mainfrom
callstack-internal:2797-bump-pusher-rebased

Conversation

@adhorodyski

@adhorodyski adhorodyski commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

We ship NWWebSocket 0.5.7 with two bugs in it, both fixed upstream. They match what our iOS clients do in production.

  1. A reconnect that can never succeedNWWebSocket#59 (0.5.8).
  2. Dead sockets that report themselves .readyNWWebSocket#62, fixed in #64 (0.5.10).

This Pusher bump pulls NWWebSocket 0.5.7 -> 0.5.10 via PusherSwift 10.1.10, so both land. PusherSwiftWithEncryption 10.1.6 shares the pod, so OldDot gets them too without a bump. Android is untouched.

Where it hits us. 1d query on start=2026-08-09T00:00:00Z end=2026-08-10T00:00:00Z, deduplicated on (email, client timestamp):

Metric Events Users Per user iOS share
DISCONNECTED -> RECONNECTING 142,924 21,793 6.56 100%
RECONNECTING -> DISCONNECTED 88,464 20,409 4.34 100%
CONNECTING -> DISCONNECTED 75,672 19,881 3.81 100%
RECONNECTING -> CONNECTED 25,708 11,327 2.27 90.4%
Socket presumed dead (iOS) 48,954 21,811 2.24 iOS only

Only 18.0% of retries reach CONNECTED — 4.6 failures per success — and 60.6% of active iOS users entered the loop that day, with zero Android lines on the first three transitions.

"[PusherConnectionManager] state change" "previous: 'DISCONNECTED' current: 'RECONNECTING'"
| extract "[App] <cts> - " | stats count_uniq(email, cts) events, count_uniq(email) users

Expect ReconnectApp to rise, not fall: a failed retry never re-subscribes, so fixing the loop converts 88,464 silent failures into successful connects.

Fixed Issues

$ #98054
PROPOSAL:

MOBILE-EXPENSIFY: https://github.com/Expensify/Mobile-Expensify/pull/14048

Tests

iOS only — nothing here changes Android or web.

  1. Sign in.
  2. Background the app for 5 minutes, foreground it, verify new chat messages arrive.
  3. Toggle airplane mode on and off, verify the socket returns.
  4. Verify Android and iOS builds pass.
  • Verify that no errors appear in the JS console

Offline tests

Not applicable — no user-facing behaviour change.

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

Same as Tests. No UI change.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

No UI change.

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

The two fixes we need are in NWWebSocket, which this bump pulls from
0.5.7 to 0.5.10 via PusherSwift 10.1.10:

- NWWebSocket 0.5.8 fixes websocket reconnection getting stuck after
  network disruption
- NWWebSocket 0.5.10 treats any POSIX error as a disconnection, fixing
  zombie connections caused by ENODATA (96) and other unrecognised codes

Production, matched 4h window: iOS runs 69,976 DISCONNECTED ->
RECONNECTING transitions across 7,833 users, of which 56,145 end in
DISCONNECTED and only 6,618 reach CONNECTED - a 9.5% retry success rate.
All three transition counts are 100% CFNetwork. Android's Java SDK
dependency is byte-identical across 1.3.1 and 1.3.5, so Android is
unaffected.

Note this may raise ReconnectApp volume rather than lower it: a socket
that currently never connects also never re-subscribes, so fixing the
retry loop converts failed attempts into successful ones.

Also fixes a dead branch in our own error plumbing. Native passed
{data: {message}} with no `type`, while PusherConnectionManager gates its
error handler on `'type' in error`, so every native Pusher error was
dropped before any log call - 0 of 63,271 error lines carried a native
user agent. Routing them through WEB_SOCKET_ERROR makes the reconnect
cause attributable, which is what lets this bump be measured rather than
assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/libs/Pusher/index.native.ts 78.94% <0.00%> (ø)
... and 9 files with indirect coverage changes

@adhorodyski
adhorodyski marked this pull request as ready for review August 10, 2026 15:08
@adhorodyski
adhorodyski requested review from a team as code owners August 10, 2026 15:08
@melvin-bot
melvin-bot Bot requested review from abzokhattab and removed request for a team August 10, 2026 15:08
@melvin-bot

melvin-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

@abzokhattab Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and removed request for a team August 10, 2026 15:08

@JmillsExpensify JmillsExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No product review required.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 638a34f715

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread package.json
"@lottiefiles/dotlottie-react": "0.13.5",
"@onfido/react-native-sdk": "15.1.0",
"@pusher/pusher-websocket-react-native": "^1.3.1",
"@pusher/pusher-websocket-react-native": "^1.3.5",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Sync the Mobile-Expensify pod lock with this bump

For HybridApp iOS builds, npm run pod-install runs from Mobile-Expensify/ios, but this commit leaves the Mobile-Expensify gitlink unchanged while changing NewDot's installed @pusher/pusher-websocket-react-native to 1.3.5. When that pinned submodule still has the old lock snapshot, CocoaPods sees the local 1.3.5 podspec requiring PusherSwift ~> 10.1.10 against the submodule's older locked PusherSwift version, so hybrid pod install/build can fail after this lands unless the paired Mobile-Expensify lock/submodule update is included or synced first.

Useful? React with 👍 / 👎.

@mountiny
mountiny self-requested a review August 10, 2026 15:24
@github-actions

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

This comment has been minimized.

@abzokhattab

Copy link
Copy Markdown
Contributor

the HybridApp iOS fails to compile as shown in the adhoc build #98052 (comment)

Mobile-Expensify/iOS/Pods/TweetNacl/Sources/CTweetNacl/ctweetnacl.c:1:10:
error: 'ctweetnacl.h' file not found (in target 'TweetNacl' from project 'Pods')

@abzokhattab

Copy link
Copy Markdown
Contributor

@MelvinBot review the PR

@MelvinBot

Copy link
Copy Markdown
Contributor

PR Review

The App-side diff looks correct, but both HybridApp adhoc builds are red, and neither failure is fixable from this PR — both live in the paired Mobile-Expensify PR Expensify/Mobile-Expensify#14048 and need to be resolved there before this can merge.

🔴 iOS HybridApp — ctweetnacl.h file not found

Root cause: PusherSwift 10.1.10 dropped its TweetNacl dependency in favor of PusherTweetNacl (~> 1.2.0) (visible in the Podfile.lock diff). For standalone NewDot that's clean — only PusherTweetNacl gets installed. But the HybridApp Podfile also pulls OldDot's PusherSwiftWithEncryption, which — if it's still on 10.1.6 — declares TweetNacl (~> 1.0.0). So CocoaPods installs both pods side by side. The build log confirms it:

Installing PusherTweetNacl (1.2.0)
Installing TweetNacl (1.0.2)

TweetNacl 1.0.2 then fails to compile (ctweetnacl.h not found) when coexisting with PusherTweetNacl, which is the fork that replaced it.

Fix (in Mobile-Expensify#14048, not here): bump PusherSwiftWithEncryption to 10.1.10 so OldDot also switches to PusherTweetNacl and TweetNacl drops out of the resolution entirely.

This also means the PR description's claim — "PusherSwiftWithEncryption 10.1.6 shares the pod, so OldDot gets them too without a bump" — is only half right: OldDot does inherit the NWWebSocket 0.5.10 bump via CocoaPods version resolution, but leaving PusherSwiftWithEncryption on 10.1.6 is exactly what pulls the stale TweetNacl pod and breaks the Hybrid build.

🔴 Android HybridApp — unrelated to the Pusher bump

This one has nothing to do with Pusher. Gradle fails evaluating settings:

Settings file '.../Mobile-Expensify/Android/settings.gradle' line: 9
> Could not read script '.../gradleUtils/PatchedArtifactsSettings.gradle' as it does not exist.

A referenced gradle script is missing on the Mobile-Expensify branch — a build-config problem in Expensify/Mobile-Expensify#14048, not caused by anything in this PR's diff.

🟢 App-side changes — look good

  • Podfile.lock, package.json / package-lock.json bumps are internally consistent (TweetNacl fully removed, PusherTweetNacl + checksums added, resolved/integrity added for the npm bump).
  • The one JS change is correct and a good catch: src/libs/Pusher/index.native.ts:85 now tags native onError with type: CONST.ERROR.WEB_SOCKET_ERROR. Without a type, the handler at PusherConnectionManager.ts:27 (if (error && 'type' in error)) was silently dropping every native error — this routes them into the WEB_SOCKET_ERROR branch so they get logged. Native onError only carries a message (no code), so it correctly can't collide with the PUSHER_ERROR 1006/4201 reconnect paths.

Notes

  • This is a native-iOS-only change; web and the JS bundle are untouched, so there was nothing web-side to verify.
  • No standalone NewDot iOS build result exists to confirm the happy path — only the (failing) HybridApp adhoc build ran. Standalone should be fine since it resolves to PusherTweetNacl alone, but worth an explicit local pod install + build check on NewDot iOS before merge.

Bottom line: hold merge until Mobile-Expensify#14048 (a) bumps PusherSwiftWithEncryption to 10.1.10 and (b) restores the missing PatchedArtifactsSettings.gradle, then re-run the adhoc build to get green iOS + Android.


🤖 Generated by MelvinBot — reply with @MelvinBot <feedback> to refine.

@quinthar quinthar added the #quality Relates to work in the #quality room label Aug 11, 2026
@mountiny

Copy link
Copy Markdown
Contributor

@TMisiukiewicz conflicts here

@abzokhattab

Copy link
Copy Markdown
Contributor

can we retrigger the adhoc @mountiny

@github-actions

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

@github-actions

Copy link
Copy Markdown
Contributor

🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
Built from App PR #98052 Mobile-Expensify PR Expensify/Mobile-Expensify#14048.

Android 🤖 iOS 🍎
https://ad-hoc-expensify-cash.s3.us-east-1.amazonaws.com/rock-artifacts/ad-hoc/rock-android-Adhoc-dec0dfd-8f4de0b1bb-c8c77662829721d54e00d3e80d2074dd93ebf62d/index.html ❌ FAILED ❌
Android The QR code can't be generated, because the iOS build failed
Web 🕸️
https://98052.pr-testing.expensify.com
Web

👀 View the workflow run that generated this build 👀

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

Labels

#quality Relates to work in the #quality room

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants