Skip to content

WebSocket.ping() on Android sends an empty binary frame instead of a protocol-level PING #57840

Open

Description

@dekpient

Description

WebSocket.ping() on Android sends an empty binary data frame (opcode 0x2) via OkHttp's
client.send(ByteString.EMPTY), instead of a real RFC 6455 PING control frame (opcode 0x9). On iOS,
ping() correctly sends a protocol-level ping via SocketRocket's sendPing:error:.

This breaks against WebSocket servers that reject binary frames (e.g. AWS API Gateway WebSocket APIs),
which close the connection with code 1003 UNSUPPORTED_DATA instead of responding with a pong.

The incorrect ping() that sends the empty frame is still present on the latest version (v0.86.2) of RN here: https://github.com/react/react-native/blob/v0.86.2/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt#L327

    try {
      client.send(ByteString.EMPTY)
    } catch (e: Exception) {
      notifyWebSocketFailed(id, e.message)
    }

Steps to reproduce

  1. Connect a WebSocket to a server that rejects binary frames (e.g. AWS API Gateway WS API).
  2. Call webSocket.ping() on Android.
  3. Observe the connection closes with 1003 UNSUPPORTED_DATA instead of receiving a pong.

React Native Version

0.86.2

Affected Platforms

Runtime - Android

Output of npx @react-native-community/cli info

System:
  OS: macOS 26.5.2
  CPU: (12) arm64 Apple M4 Pro
  Memory: 106.66 MB / 24.00 GB
  Shell:
    version: "5.9"
    path: /opt/homebrew/bin/zsh
Binaries:
  Node:
    version: 23.10.0
    path: /opt/homebrew/bin/node
  Yarn:
    version: 1.22.19
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.9.2
    path: /opt/homebrew/bin/npm
  Watchman:
    version: 2025.05.19.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods: Not Found
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.2
      - iOS 26.2
      - macOS 26.2
      - tvOS 26.2
      - visionOS 26.2
      - watchOS 26.2
  Android SDK: Not Found
IDEs:
  Android Studio: 2024.2 AI-242.23726.103.2422.12816248
  Xcode:
    version: 26.2/17C52
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.14
    path: /Users/inging/.jenv/shims/javac
  Ruby: Not Found
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: Not found
  newArchEnabled: Not found
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Stacktrace or Logs

Logs from mitmproxy with a custom interceptor script:


<IP_REDACTED>:44604 -> WebSocket binary message -> <WSS_HOST_REDACTED>:443/
Error in WebSocket connection to <WSS_HOST_REDACTED>:443: WebSocket Error: UNSUPPORTED_DATA (reason: Binary is not supported)

MANDATORY Reproducer

https://gist.github.com/dekpient/4b611eb4efb58ecc1a881942f842a595

Screenshots and Videos

No response

Activity

  1. react-native-bot commented on Aug 5, 2026

    @react-native-bot
    Collaborator

    Warning

    Missing reproducer: We could not detect a reproducible example in your issue report. Reproducers are mandatory and we can accept only one of those as a valid reproducer:


    You can read more about about it on our website: How to report a bug.

  2. SnowingFox commented on Aug 10, 2026

    @SnowingFox
    Contributor

    Triage finding: this can't be fixed in react-native without upstream OkHttp work

    Confirmed the bug is still present on main (WebSocketModule.kt:406 — client.send(ByteString.EMPTY) sends a BINARY frame, opcode 0x2, not a PING control frame 0x9). iOS is correct (RCTWebSocketModule.mm → SocketRocket sendPing:error:).

    Root cause is upstream, not RN:

    • No OkHttp version exposes a public ping. I inspected 4.9.2 (RN's pinned version), 5.1.0, and 5.4.0: okhttp3.WebSocket only has request()/queueSize()/send(String)/send(ByteString)/close()/cancel(). send(ByteString) is hard-wired to opcode 0x2, so a protocol-level PING (0x9) is only reachable via okhttp3.internal.ws.RealWebSocket.writePingFrame().
    • That internal path is a real thread-safety hazard here: writePingFrame() is meant to run only on the connection's serialized TaskRunner thread; RN's ping() runs on the JS bridge thread, so calling it races the writer and can interleave/corrupt frames on the socket. RN production code also never touches okhttp3.internal.
    • Upstream is tracking this: sendPing and onPong lysine-dev/okhttp#7370 (public sendPing/onPong proposal) is still open/unmerged; maintainers' documented stance is "browsers don't do pings" (WebSocket ping logic is not customizable lysine-dev/okhttp#3197). So an OkHttp upgrade today would not help either.

    Options for maintainers:

    1. Short-term: RN's ping() is undocumented (not part of the standard WebSocket API), so making it a no-op on Android is defensible — but it would not give AWS-keepalive users what they need.
    2. Real fix: unblock sendPing and onPong lysine-dev/okhttp#7370 upstream, then adopt public sendPing() on an OkHttp upgrade.

    This needs an upstream decision rather than an RN-only PR.

  3. react-native-bot commented on Sep 4, 2026

    @react-native-bot
    Collaborator

    This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days.

  4. added
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    and removed
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Sep 4, 2026
  5. react-native-bot commented on Sep 28, 2026

    @react-native-bot
    Collaborator

    This issue is waiting for author's feedback since 24 days. Please provide the requested feedback or this will be closed in 7 days.

  6. added
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    and removed
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions