react-native: 0.72: websockets malformed authorization headers only on iOS

Description

After upgrading from react-native@0.71.11 to 0.72.0 I get this error response from Hasura 2.27.0 on iOS, on Android it works fine.

socket closed : code 4400, reason : {"server_error_msg":"4400: Connection initialization failed: Malformed Authorization header"}

I’m not quite sure if the issue relies on graphql-ws or if something has changed on react-native that affects graphql-ws somehow. This is happening on both ws:localhost and wss:// on Testflight builds.

this is the GraphQLWsLink on "@apollo/client": "3.7.16" with "graphql-ws": "^5.13.1"

 const wsLink = new GraphQLWsLink(
      createClient({
        url: webSocketUri,
        lazy: true,
        shouldRetry: () => true,
        retryAttempts: Infinity,
        retryWait: (_count) => new Promise((r) => setTimeout(() => r(), 1000)),
        on: {
          ping: (received) => {
            if (!received /* sent */) {
              timedOut = setTimeout(() => {
                /* a close event `4499: Terminated` is issued to the current WebSocket and an
                 artificial `{ code: 4499, reason: 'Terminated', wasClean: false }` close-event-like
                 object is immediately emitted without waiting for the one coming from `WebSocket.onclose`
                 calling terminate is not considered fatal and a connection retry will occur as expected
                 see: https://github.com/enisdenjo/graphql-ws/discussions/290
                 */
                wsLink.client.terminate();
              }, 5000);
            }
          },
          pong: (received) => {
            if (received) {
              clearTimeout(timedOut);
            }
          },
          closed: (error: any) => {
            console.log("socket closed : code %s, reason : %s", error.code, error.reason);
            if (error.code !== 1000) {
              disconnectedVar(true);
            }
          },
          connected: () => {
            if (disconnectedVar()) {
              disconnectedVar(false);
            }
          },
          error: (err: any) => {
            console.log(`error in sockets, code: ${err.code}, reason: ${err.reason}`);
          }
        },
        connectionParams: async () => {
          const accessToken = await getAccessTokenRefresh();
          if (accessToken?.token) {
            return {
              headers: {
                Authorization: `Bearer ${accessToken.token}`
              }
            };
          }
          return {};
        }
      })
    );

I’ve also opened an issue on graphql-ws repo: https://github.com/enisdenjo/graphql-ws/issues/485

React Native Version

0.72.0

Output of npx react-native info

System: OS: macOS 13.4 CPU: (20) arm64 Apple M1 Ultra Memory: 613.11 MB / 64.00 GB Shell: version: “5.9” path: /bin/zsh Binaries: Node: version: 18.16.0 path: ~/.nvm/versions/node/v18.16.0/bin/node Yarn: version: 1.22.19 path: ~/.nvm/versions/node/v18.16.0/bin/yarn npm: version: 9.7.1 path: ~/.nvm/versions/node/v18.16.0/bin/npm Watchman: version: 2023.04.10.00 path: /opt/homebrew/bin/watchman Managers: CocoaPods: version: 1.12.1 path: /opt/homebrew/opt/ruby@2.7/bin/pod SDKs: iOS SDK: Platforms: - DriverKit 22.4 - iOS 16.4 - macOS 13.3 - tvOS 16.4 - watchOS 9.4 Android SDK: Not Found IDEs: Android Studio: 2022.2 AI-222.4459.24.2221.10121639 Xcode: version: 14.3.1/14E300c path: /usr/bin/xcodebuild Languages: Java: version: 17.0.6 path: /usr/bin/javac Ruby: version: 2.7.8 path: /opt/homebrew/opt/ruby@2.7/bin/ruby npmPackages: “@react-native-community/cli”: Not Found react: installed: 18.2.0 wanted: ^18.2.0 react-native: installed: 0.72.0 wanted: 0.72.0 react-native-macos: Not Found npmGlobalPackages: “react-native”: Not Found Android: hermesEnabled: true newArchEnabled: false iOS: hermesEnabled: true newArchEnabled: false

Steps to reproduce

Nothing special, just upgraded from react-native 0.71.11 to 0.72.0

Snack, code example, screenshot, or link to a repository

no snack, it needs a minimal Hasura or other websocket API, sorry 😦

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 18 (6 by maintainers)

Most upvoted comments

In the meanwhile, for anyone that can’t wait for this to land on main here’s how to patch it with cocoapods-patch plugin:

notice: using direct gem 'cocoapods-patch', git: 'https://github.com/crherman7/cocoapods-patch.git', branch: 'feat/cocoapods_1.12.0' on Gemfile breaks the pod install so this is the only way to go I guess.

on Podfile add this plugin right below platform :ios, '13.0'

plugin 'cocoapods-patch' 

on Gemfile add:

gem 'specific_install'
gem 'cocoapods-patch'

install bundles:

bundle install

install this gem from command line to download the cocoapods-patch that supports cocoapods@1.12.x (PR)

gem specific_install 'https://github.com/crherman7/cocoapods-patch.git' -b 'feat/cocoapods_1.12.0'

open Xcode or find this file and open it in an editor: (or create SocketRocket+0.6.0.diff under ios/patches and paste the diff you’ll find below into it and simply run pod install afterwards)

/Users/stathis/IdeaProjects/<your_app_name>/ios/Pods/SocketRocket/SocketRocket/Internal/Utilities/SRURLUtilities.m

Apply the changes from the PR on line 44:

extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url)
{
    if (!url.user || !url.password) {
        return nil;
    }

    NSData *data = [[NSString stringWithFormat:@"%@:%@", url.user, url.password] dataUsingEncoding:NSUTF8StringEncoding];
    return [NSString stringWithFormat:@"Basic %@", SRBase64EncodedStringFromData(data)];
}

on terminal go to ios folder and run:

pod patch create SocketRocket 

A patch will be applied and stored under ios/patches folder so everytime you run pod install it will be applied.

SocketRocket+0.6.0.diff:

diff --git a/cocoapods-patch-20230622-18946-1pc4h0/SocketRocket/SocketRocket/Internal/Utilities/SRURLUtilities.m b/Pods/SocketRocket/SocketRocket/Internal/Utilities/SRURLUtilities.m
index 2f66d8d9..37ec2e08 100644
--- a/cocoapods-patch-20230622-18946-1pc4h0/SocketRocket/SocketRocket/Internal/Utilities/SRURLUtilities.m
+++ b/Pods/SocketRocket/SocketRocket/Internal/Utilities/SRURLUtilities.m
@@ -43,6 +43,10 @@ extern BOOL SRURLRequiresSSL(NSURL *url)
 
 extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url)
 {
+    if (!url.user || !url.password) {
+        return nil;
+    }
+
     NSData *data = [[NSString stringWithFormat:@"%@:%@", url.user, url.password] dataUsingEncoding:NSUTF8StringEncoding];
     return [NSString stringWithFormat:@"Basic %@", SRBase64EncodedStringFromData(data)];
 }

if you’re using a CI tool then create a step and add this line on bash right after bundle install step

gem specific_install 'https://github.com/crherman7/cocoapods-patch.git' -b 'feat/cocoapods_1.12.0'

I’ll try to reach out to the team handling the library to see if we can push a release soon!

So, we can’t really use SocketRocket 0.7.0 because Flipper depends on SocketRocket ~> 0.6.0, and we can’t bump Flipper for major compat issues with Android’s NDK. So we pushed a SocketRocket 0.6.1 with the fix and we are releasing 0.72.1 with SocketRocket 0.6.1. It should be out in some hours and you won’t need patches anymore! 😄

Hi @efstathiosntonas, thanks for reporting. So we have to push SocketRocket team to merge the diff, make a release and update the dependency, probably.

It occurs under the exact same conditions however. @carlosalmonte04

My websocket connections work for Android, Web and even on iOS for ws:// links

Only the secure wss:// seems to break for iOS.

@cipolleschi thanks for the update, I’m not using Flipper because of Firebase so I never saw this compatibility issue.

@cipolleschi thanks, it took me a good amount of time to track this down 😅

I don’t know if that PR needs extra testing, it just fulfills my current authentication flow.