React-native: WebSocket module for Android crashes app when it fails to connect

Created on 11 Oct 2015  Â·  50Comments  Â·  Source: facebook/react-native

When creating a new WebSocket connection, if the var ws = new WebSocket('....'); call fails to connect to the server, it will crash the app by throwing an error with message Cannot close WebSocket. Unknown WebSocket id 0

This is because the websocketFailed event fires, which calls this._closeWebSocket(id); inside WebSocket.js. Since the connection was never open to begin with, calling WebSocket client = mWebSocketConnections.get(id); inside WebSocketModule.java will return null, hence triggering the exception throw new RuntimeException("Cannot close WebSocket. Unknown WebSocket id " + id);

Locked

Most helpful comment

my problem :
in javascript, the socket instance is connected and readyState is OPEN, but when i send a message, android app crash
my solution is copy WebSocketModule.java as a CustomModule

if (client == null) {
            // This is a programmer error
            //throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
            mWebSocketConnections.remove(id);
            notifyWebSocketFailed(id, "Unknown WebSocket id");
            return;
        }

All 50 comments

Just to confirm, this is on master?

cc @satya164

Two things we should do: fix the bug and make the native layer uncrashable.

@ide yes, it is on master. I will submit a 1 line PR to fix this bug in the mean time, as it makes WebSockets unusable on Android at the moment.

@yzarubin Than you.

The native layer still crashes with no way to prevent it from js. this happens for me when I enable airplane mode after making a websocket connection. I send a keepalive message every few seconds and when it fails because there is no connection I get this error.
RN ~0.42.3

09-04 16:54:00.198 23229 23262 E AndroidRuntime: java.lang.RuntimeException: Cannot send a message. Unknown WebSocket id 2
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at com.facebook.react.modules.websocket.WebSocketModule.send(WebSocketModule.java:221)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at java.lang.reflect.Method.invoke(Native Method)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at com.facebook.react.bridge.BaseJavaModule$JavaMethod.invoke(BaseJavaModule.java:345)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at com.facebook.react.cxxbridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:136)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at android.os.Handler.handleCallback(Handler.java)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:31)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at android.os.Looper.loop(Looper.java)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:196)
09-04 16:54:00.198 23229 23262 E AndroidRuntime:    at java.lang.Thread.run(Thread.java:818)
09-04 16:54:00.208  3551  4476 W ActivityManager:   Force finishing activity com.chipta/.MainActivity

I tried a try / catch around the ws.send and also a if this.ws but they both don't prevent this error so I'm kind of stuck. How do I check if I can send a message if the ws object does not give me this information?

edit: It seems possible to prevent this if I check for an internet connection before sending. But it should just raise a js error like a normal http request

@ThaJay i got this error too. People are working with the app outside while they're driving (bad network most of the time)
How can we prevent the crash anyway ? 🤕

My comment already states how to prevent the crash:

It seems possible to prevent this if I check for an internet connection before sending. But it should just raise a js error like a normal http request

This should still error in the js and not in the java. Java errors are impossible to deal with from js. So I don't get why this issue was closed. @ide

I just run into this issue in the alpha version of our production app. It's not something widespread yet, one user experienced it two times, but I also would like to know why this was closed when it's still an issue (after 2 years of reporting it).

Sadly I don't have any additional information in helping to solve it. Websockets are used inside subscriptions-transport-ws in my case.

image

We have hit this as well in our app that is currently in beta, but it is our second most hit crash already. We use websockets in a couple places, and we do the correct hardening in those locations in the javascript. It seems like a bug that the native layer would throw such an exception

We are also experiencing this crash. It happens quite a lot , about 2%-3% of the sessions, to thousands of users.
It seems like this is an ongoing issue, maybe it should be re-open?

Reopening since this is somewhat core and results in crashes.

We have this problem Cannot send a message. Unknown WebSocket id ....
crashes sometimes when app starts.

"react": "16.0.0-alpha.12",
"react-i18next": "5.3.0",
"react-native": "0.48.3",
"socket.io": "2.0.3",
"socket.io-client": "2.0.3",

fabric crashlytics stacktrace attached.
stacktrace.txt

Same. I get this issue both in debug and production releases on Google Play. I only get this issue on Android.
"react": "^16.0.0",
"react-native": "^0.49.5"

skaermbillede 2017-11-05 kl 12 14 51

This issue might be related to #8949

*** Crash Log Head ****
Device Manufacturer: Freescale
Device Model : INBOX310
Android Version : 4.2.2
Android SDK : 17
App VersionName : 0.8.7
App VersionCode : 2
*** Crash Log Head ****

java.lang.RuntimeException: Cannot send a message. Unknown WebSocket id 0
at com.facebook.react.modules.websocket.WebSocketModule.send(WebSocketModule.java:199)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:363)
at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:162)
at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:31)
at android.os.Looper.loop(Looper.java:137)
at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:194)
at java.lang.Thread.run(Thread.java:856)

https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java#L228

It looks to me as if this exception is the cause of the issue, is there a way we can catch this in JS? throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);

I was thinking of just forking RN and just catching this exception natively just to keep my users from experiencing crashes

I was thinking of just forking RN and just catching this exception natively

Or just send a PR?

my problem :
in javascript, the socket instance is connected and readyState is OPEN, but when i send a message, android app crash
my solution is copy WebSocketModule.java as a CustomModule

if (client == null) {
            // This is a programmer error
            //throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
            mWebSocketConnections.remove(id);
            notifyWebSocketFailed(id, "Unknown WebSocket id");
            return;
        }

native close this socket and let react-native js websocket.onerror or onclose
if (client == null) { //close this websocket to js websocket.error }

+1

This really needs to be adressed. Apps using the react native websockets in this state just can't be published, because they are simply broken. You always can check the ready state before sending, but it seems that this isn't really reliable. Then when you socket.send() you get the crash.

This really needs to be adressed

Send a pull request

I'm a Javascript developer, no Java developer. The fact that I understand Java doesn't mean I'm capable of analyzing the situation and provide a propper solution. I just wanted to point out, that currently there shouldn't be a single RN app using RN's websockets in the store, as they just break the app.

@zxyah How do you create your own custom module in Android, and make sure it loads that module instead of React Native's when importing (new to Android)?.

@danieldelouya Copy the source java code as your custom socket module and fix the bug, copy the source javascript code as your custom socket component, it required your customer socket module, if the platform === ios return default socket, else return your custom socket, then where you need socket, require your custom socket

Copy the source java code as your custom socket module and fix the bug

Really strange that you copy the code to fix it, but don't wanna send a pull request.

Not sure I figured it out. A PR would be nice indeed @zxyah

@zxyah "copy the source javascript code as your custom socket component, it required your customer socket module" - where? Do you have an example of how to do it?

Not sure how to create a custom component and make sure when importing Websocket in React Native that it chooses the custom one for android

@zxyah Can you please provide an example of how you did it. I have a hard time forking react native and getting that properly installed, so your solution seems best.
It's the most common crash in my app on Android and I need to solve this ASAP

@danieldelouya
React-Native Version:0.47.2
Module:

public class HxSocketModule extends ReactContextBaseJavaModule {
    private final Map<Integer, WebSocket> mWebSocketConnections = new HashMap<>();

    private ReactContext mReactContext;
    private ForwardingCookieHandler mCookieHandler;

    public HxSocketModule(ReactApplicationContext context) {
        super(context);
        mReactContext = context;
        mCookieHandler = new ForwardingCookieHandler(context);
    }

    private void sendEvent(String eventName, WritableMap params) {
        mReactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    @Override
    public String getName() {
        return "HxSocket";
    }

    @ReactMethod
    public void connect(
            final String url,
            @Nullable final ReadableArray protocols,
            @Nullable final ReadableMap headers,
            final int id) {
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
                .build();

        Request.Builder builder = new Request.Builder()
                .tag(id)
                .url(url);

        String cookie = getCookie(url);
        if (cookie != null) {
            builder.addHeader("Cookie", cookie);
        }

        if (headers != null) {
            ReadableMapKeySetIterator iterator = headers.keySetIterator();

            if (!headers.hasKey("origin")) {
                builder.addHeader("origin", getDefaultOrigin(url));
            }

            while (iterator.hasNextKey()) {
                String key = iterator.nextKey();
                if (ReadableType.String.equals(headers.getType(key))) {
                    builder.addHeader(key, headers.getString(key));
                } else {
                    FLog.w(
                            ReactConstants.TAG,
                            "Ignoring: requested " + key + ", value not a string");
                }
            }
        } else {
            builder.addHeader("origin", getDefaultOrigin(url));
        }

        if (protocols != null && protocols.size() > 0) {
            StringBuilder protocolsValue = new StringBuilder("");
            for (int i = 0; i < protocols.size(); i++) {
                String v = protocols.getString(i).trim();
                if (!v.isEmpty() && !v.contains(",")) {
                    protocolsValue.append(v);
                    protocolsValue.append(",");
                }
            }
            if (protocolsValue.length() > 0) {
                protocolsValue.replace(protocolsValue.length() - 1, protocolsValue.length(), "");
                builder.addHeader("Sec-WebSocket-Protocol", protocolsValue.toString());
            }
        }

        client.newWebSocket(builder.build(), new WebSocketListener() {

            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                mWebSocketConnections.put(id, webSocket);
                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                sendEvent("websocketOpen", params);
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                params.putInt("code", code);
                params.putString("reason", reason);
                sendEvent("websocketClosed", params);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                notifyWebSocketFailed(id, t.getMessage());
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                params.putString("data", text);
                params.putString("type", "text");
                sendEvent("websocketMessage", params);
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                String text = bytes.base64();
                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                params.putString("data", text);
                params.putString("type", "binary");
                sendEvent("websocketMessage", params);
            }
        });

        // Trigger shutdown of the dispatcher's executor so this process can exit cleanly
        client.dispatcher().executorService().shutdown();
    }

    @ReactMethod
    public void close(int code, String reason, int id) {
        WebSocket client = mWebSocketConnections.get(id);
        if (client == null) {
            // WebSocket is already closed
            // Don't do anything, mirror the behaviour on web
            return;
        }
        try {
            client.close(code, reason);
            mWebSocketConnections.remove(id);
        } catch (Exception e) {
            FLog.e(
                    ReactConstants.TAG,
                    "Could not close WebSocket connection for id " + id,
                    e);
        }
    }

    @ReactMethod
    public void send(String message, int id) {
        WebSocket client = mWebSocketConnections.get(id);
        if (client == null) {
            // This is a programmer error
            //throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
            mWebSocketConnections.remove(id);
            notifyWebSocketFailed(id, "hxSocket is null while send");
            return;
        }
        try {
            client.send(message);
        } catch (Exception e) {
            notifyWebSocketFailed(id, e.getMessage());
        }
    }

    @ReactMethod
    public void sendBinary(String base64String, int id) {
        WebSocket client = mWebSocketConnections.get(id);
        if (client == null) {
            // This is a programmer error
            //throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
            mWebSocketConnections.remove(id);
            notifyWebSocketFailed(id, "hxSocket is null while send binary");
            return;
        }
        try {
            client.send(ByteString.decodeBase64(base64String));
        } catch (Exception e) {
            notifyWebSocketFailed(id, e.getMessage());
        }
    }

    @ReactMethod
    public void ping(int id) {
        WebSocket client = mWebSocketConnections.get(id);
        if (client == null) {
            // This is a programmer error
            //throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
            mWebSocketConnections.remove(id);
            notifyWebSocketFailed(id, "hxSocket is null while ping");
            return;
        }
        try {
            client.send(ByteString.EMPTY);
        } catch (Exception e) {
            notifyWebSocketFailed(id, e.getMessage());
        }
    }

    private void notifyWebSocketFailed(int id, String message) {
        WritableMap params = Arguments.createMap();
        params.putInt("id", id);
        params.putString("message", message);
        sendEvent("websocketFailed", params);
    }

    /**
     * Get the default HTTP(S) origin for a specific WebSocket URI
     *
     * @param uri
     * @return A string of the endpoint converted to HTTP protocol (http[s]://host[:port])
     */

    private static String getDefaultOrigin(String uri) {
        try {
            String defaultOrigin;
            String scheme = "";

            URI requestURI = new URI(uri);
            if (requestURI.getScheme().equals("wss")) {
                scheme += "https";
            } else if (requestURI.getScheme().equals("ws")) {
                scheme += "http";
            } else if (requestURI.getScheme().equals("http") || requestURI.getScheme().equals("https")) {
                scheme += requestURI.getScheme();
            }

            if (requestURI.getPort() != -1) {
                defaultOrigin = String.format(
                        "%s://%s:%s",
                        scheme,
                        requestURI.getHost(),
                        requestURI.getPort());
            } else {
                defaultOrigin = String.format("%s://%s/", scheme, requestURI.getHost());
            }

            return defaultOrigin;
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("Unable to set " + uri + " as default origin header");
        }
    }

    /**
     * Get the cookie for a specific domain
     *
     * @param uri
     * @return The cookie header or null if none is set
     */
    private String getCookie(String uri) {
        try {
            URI origin = new URI(getDefaultOrigin(uri));
            Map<String, List<String>> cookieMap = mCookieHandler.get(origin, new HashMap());
            List<String> cookieList = cookieMap.get("Cookie");

            if (cookieList == null || cookieList.isEmpty()) {
                return null;
            }

            return cookieList.get(0);
        } catch (URISyntaxException | IOException e) {
            throw new IllegalArgumentException("Unable to get cookie from " + uri);
        }
    }
}

Package:

public class HxSocketPackage implements ReactPackage {
    // Deprecated RN 0.47
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new HxSocketModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

WebSocketEvent.js

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 */

"use strict";

/**
 * Event object passed to the `onopen`, `onclose`, `onmessage`, `onerror`
 * callbacks of `WebSocket`.
 *
 * The `type` property is "open", "close", "message", "error" respectively.
 *
 * In case of "message", the `data` property contains the incoming data.
 */
class WebSocketEvent {
  constructor(type, eventInitDict) {
    this.type = type.toString();
    Object.assign(this, eventInitDict);
  }
}

module.exports = WebSocketEvent;

WebSocket.js

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
"use strict";

import { NativeEventEmitter, Platform, NativeModules } from "react-native";

import EventTarget from "event-target-shim";
const base64 = require("base64-js");
const RCTWebSocketModule = NativeModules.HxSocket;
import WebSocketEvent from "./WebSocketEvent";

function binaryToBase64(data) {
  if (data instanceof ArrayBuffer) {
    data = new Uint8Array(data);
  }
  if (data instanceof Uint8Array) {
    return base64.fromByteArray(data);
  }
  if (!ArrayBuffer.isView(data)) {
    throw new Error("data must be ArrayBuffer or typed array");
  }
  const { buffer, byteOffset, byteLength } = data;
  return base64.fromByteArray(new Uint8Array(buffer, byteOffset, byteLength));
}

const CONNECTING = 0;
const OPEN = 1;
const CLOSING = 2;
const CLOSED = 3;

const CLOSE_NORMAL = 1000;

const WEBSOCKET_EVENTS = ["close", "error", "message", "open"];

let nextWebSocketId = 0;

/**
 * Browser-compatible WebSockets implementation.
 *
 * See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
 * See https://github.com/websockets/ws
 */
class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
  static CONNECTING = CONNECTING;
  static OPEN = OPEN;
  static CLOSING = CLOSING;
  static CLOSED = CLOSED;

  CONNECTING: number = CONNECTING;
  OPEN: number = OPEN;
  CLOSING: number = CLOSING;
  CLOSED: number = CLOSED;

  _socketId: number;
  _eventEmitter: NativeEventEmitter;
  _subscriptions: Array;

  onclose: ?Function;
  onerror: ?Function;
  onmessage: ?Function;
  onopen: ?Function;

  binaryType: ?string;
  bufferedAmount: number;
  extension: ?string;
  protocol: ?string;
  readyState: number = CONNECTING;
  url: ?string;

  // This module depends on the native `RCTWebSocketModule` module. If you don't include it,
  // `WebSocket.isAvailable` will return `false`, and WebSocket constructor will throw an error
  static isAvailable: boolean = !!RCTWebSocketModule;

  constructor(
    url: string,
    protocols: ?string | ?Array<string>,
    options: ?{ origin?: string }
  ) {
    super();
    if (typeof protocols === "string") {
      protocols = [protocols];
    }

    if (!Array.isArray(protocols)) {
      protocols = null;
    }

    if (!WebSocket.isAvailable) {
      throw new Error(
        "Cannot initialize WebSocket module. " +
          "Native module RCTWebSocketModule is missing."
      );
    }

    this._eventEmitter = new NativeEventEmitter(RCTWebSocketModule);
    this._socketId = nextWebSocketId++;
    this._registerEvents();
    RCTWebSocketModule.connect(url, protocols, options, this._socketId);
  }

  close(code?: number, reason?: string): void {
    if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
      return;
    }

    this.readyState = this.CLOSING;
    this._close(code, reason);
  }

  send(data): void {
    if (this.readyState === this.CONNECTING) {
      throw new Error("INVALID_STATE_ERR");
    }

    if (typeof data === "string") {
      RCTWebSocketModule.send(data, this._socketId);
      return;
    }

    if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
      RCTWebSocketModule.sendBinary(binaryToBase64(data), this._socketId);
      return;
    }

    throw new Error("Unsupported data type");
  }

  ping(): void {
    if (this.readyState === this.CONNECTING) {
      throw new Error("INVALID_STATE_ERR");
    }

    RCTWebSocketModule.ping(this._socketId);
  }

  _close(code?: number, reason?: string): void {
    if (Platform.OS === "android") {
      // See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
      const statusCode = typeof code === "number" ? code : CLOSE_NORMAL;
      const closeReason = typeof reason === "string" ? reason : "";
      RCTWebSocketModule.close(statusCode, closeReason, this._socketId);
    } else {
      RCTWebSocketModule.close(this._socketId);
    }
  }

  _unregisterEvents(): void {
    this._subscriptions.forEach(e => e.remove());
    this._subscriptions = [];
  }

  _registerEvents(): void {
    this._subscriptions = [
      this._eventEmitter.addListener("websocketMessage", ev => {
        if (ev.id !== this._socketId) {
          return;
        }
        this.dispatchEvent(
          new WebSocketEvent("message", {
            data:
              ev.type === "binary"
                ? base64.toByteArray(ev.data).buffer
                : ev.data
          })
        );
      }),
      this._eventEmitter.addListener("websocketOpen", ev => {
        if (ev.id !== this._socketId) {
          return;
        }
        this.readyState = this.OPEN;
        this.dispatchEvent(new WebSocketEvent("open"));
      }),
      this._eventEmitter.addListener("websocketClosed", ev => {
        if (ev.id !== this._socketId) {
          return;
        }
        this.readyState = this.CLOSED;
        this.dispatchEvent(
          new WebSocketEvent("close", {
            code: ev.code,
            reason: ev.reason
          })
        );
        this._unregisterEvents();
        this.close();
      }),
      this._eventEmitter.addListener("websocketFailed", ev => {
        if (ev.id !== this._socketId) {
          return;
        }
        this.readyState = this.CLOSED;
        this.dispatchEvent(
          new WebSocketEvent("error", {
            message: ev.message
          })
        );
        this.dispatchEvent(
          new WebSocketEvent("close", {
            message: ev.message
          })
        );
        this._unregisterEvents();
        this.close();
      })
    ];
  }
}

module.exports = WebSocket;

@zxyah Thank you :)

@zxyah Do you happen to know I one can simply edit WebSocketModule.java file in the react-native node_module and right before submitting the app to app store? If this is possible it'll be the easiest way to go for me. I know one would have to edit the file every time you run npm install, but I'm not sure whether it actually stays that way once app is uploaded.

Here is a WebSocketModule that fixes the issue. Since everything is private it has to be done with reflection.

You need to remove the standard Native Module, and replace it with this:

private class CustomWebSocketModule extends WebSocketModule {

        private Map<Integer, WebSocket> superWebSocketConnections;
        private Method notifyWebSocketFailedMethod;

        CustomWebSocketModule(final ReactApplicationContext context) {
            super(context);
            try {
                Field mWebSocketConnectionsField = getClass().getSuperclass().getDeclaredField("mWebSocketConnections");
                mWebSocketConnectionsField.setAccessible(true);
                this.superWebSocketConnections = (Map<Integer, WebSocket>) mWebSocketConnectionsField.get(this);
                Class[] types = {Integer.TYPE, String.class};
                this.notifyWebSocketFailedMethod = getClass().getSuperclass().getDeclaredMethod("notifyWebSocketFailed", types);
                this.notifyWebSocketFailedMethod.setAccessible(true);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        @ReactMethod
        public void connect(String url, @javax.annotation.Nullable ReadableArray protocols, @javax.annotation.Nullable ReadableMap headers, int id) {
            super.connect(url, protocols, headers, id);
        }

        @Override
        @ReactMethod
        public void close(int code, String reason, int id) {
            super.close(code, reason, id);
        }

        @Override
        @ReactMethod
        public void sendBinary(String base64String, int id) {
            super.sendBinary(base64String, id);
        }

        @Override
        @ReactMethod
        public void ping(int id) {
            super.ping(id);
        }

        @Override
        @ReactMethod
        public void send(String message, int id) {
            try {
                WebSocket client = this.superWebSocketConnections.get(id);
                if (client == null) {
                    this.superWebSocketConnections.remove(id);
                    this.notifyWebSocketFailedMethod.invoke(this, id, "Unknown WebSocket id");
                    return;
                } else {
                    super.send(message, id);
                }
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    } 

I have found one of problems is due to HashMap used for mWebSocketConnections in WebSocketModule.java.

HashMap is not thread-safe, and can lose socket clients if concurrent sockets are opened at the same time.

If you can build RN from android source, just try this:

import java.util.concurrent.ConcurrentHashMap;

// change from HashMap to ConcurrentHashMap
private final Map<Integer, WebSocket> mWebSocketConnections = new ConcurrentHashMap<>();
private final Map<Integer, ContentHandler> mContentHandlers = new ConcurrentHashMap<>();

// and more safety:
  @ReactMethod
  public void send(String message, int id) {
    WebSocket client = mWebSocketConnections.get(id);
    if (client == null) {
      // This is a programmer error
      // throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);

        // just notify error
        WritableMap params = Arguments.createMap();
        params.putInt("id", id);
        params.putString("message", "client is null");
        sendEvent("websocketFailed", params);
        params = Arguments.createMap();
        params.putInt("id", id);
        params.putInt("code", 0);
        params.putString("reason", "client is null");
        sendEvent("websocketClosed", params);
        mWebSocketConnections.remove(id);
        mContentHandlers.remove(id);
        return;
    }
    try {
      client.send(message);
    } catch (Exception e) {
      notifyWebSocketFailed(id, e.getMessage());
    }
  }

That sounds more like a solution than just changing the way how the error is thrown/bubbled down to JS. My experiments also showed, that often the connection shouldn't have gone, but the runtime exception is thrown. So dispatching an error event in those cases would only move the bug to JS where it can't be solved but just workarounded. It also occours, that the close event is dispatched before the open event, this situation also leads to the point where the uncatchable runtime exception is thrown.

+1 Any updates in this one?

@tanthanh289 can you send a pull request please? it takes very few minutes to send a PR with your changes if you have already fixed it and helps everyone.

+1

+1

Please stop commenting with +1. It sends a notification to everyone in the thread and doesn't achieve anything to resolve the issue.

Many people have commented here with the fix, (especially @tanthanh289) and it'll be useful if someone sends a pull request with that change to fix it. If this issue affects you, please send a pull request and mention me, and I'll try to get it merged.

@satya164, I've created PR #17884 to address this issue. Would like to greatly credit @tanthanh289 for inspiring this fix.

Hello all,

I am also facing the same issue the app is running fine in iOS but getting crashed in Android. Few days back it was working fine, but suddenly I started getting this issue. Please assist me.

Thanks

Closing this since the PR has been merged already.

Has anyone seen this error before

"websocketFailed",{"message":"unexpected end of stream on Connection{10.0.2.2:8080, proxy=DIRECT hostAddress=/10.0.2.2:8080 cipherSuite=none protocol=http/1.1}","id":3

+1

Has anyone seen this error before

"websocketFailed",{"message":"unexpected end of stream on Connection{10.0.2.2:8080, proxy=DIRECT hostAddress=/10.0.2.2:8080 cipherSuite=none protocol=http/1.1}","id":3

Do you solve it?How to do? @jmsbrett

@s349856186

I did not solve it.

+1

It's still happening

+1 - ios as well

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jlongster picture jlongster  Â·  3Comments

oney picture oney  Â·  3Comments

axelg12 picture axelg12  Â·  3Comments

TrakBit picture TrakBit  Â·  3Comments

despairblue picture despairblue  Â·  3Comments