-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebSocket module for Android crashes app when it fails to connect #3346
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. |
…acebook#3346 Summary: Check that the WS state is set to OPEN before trying to close it when the ```websocketFailed``` event fires. Otherwise the app throws an error at the Android level. Fixes facebook#3346 Closes facebook#3347 Reviewed By: @svcscm Differential Revision: D2535807 Pulled By: @mkonicek fb-gh-sync-id: bb70c551ea2e582cfaa80139a265dbbca6d990d2
…acebook#3346 Summary: Check that the WS state is set to OPEN before trying to close it when the ```websocketFailed``` event fires. Otherwise the app throws an error at the Android level. Fixes facebook#3346 Closes facebook#3347 Reviewed By: @svcscm Differential Revision: D2535807 Pulled By: @mkonicek fb-gh-sync-id: bb70c551ea2e582cfaa80139a265dbbca6d990d2
…acebook#3346 Summary: Check that the WS state is set to OPEN before trying to close it when the ```websocketFailed``` event fires. Otherwise the app throws an error at the Android level. Fixes facebook#3346 Closes facebook#3347 Reviewed By: @svcscm Differential Revision: D2535807 Pulled By: @mkonicek fb-gh-sync-id: bb70c551ea2e582cfaa80139a265dbbca6d990d2
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.
I tried a 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) |
My comment already states how to prevent the crash:
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. |
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. |
Reopening since this is somewhat core and results in crashes. |
We have this problem
fabric crashlytics stacktrace attached. |
This issue might be related to #8949 |
************* Crash Log Head **************** java.lang.RuntimeException: Cannot send a message. Unknown WebSocket id 0 |
It looks to me as if this exception is the cause of the issue, is there a way we can catch this in JS? I was thinking of just forking RN and just catching this exception natively just to keep my users from experiencing crashes |
Or just send a PR? |
my problem : 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 |
+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. |
@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. |
@danieldelouya 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 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 similar comment
+1 |
Please stop commenting with 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. |
Summary: … prevent unknown websocket IDs from crashing on Android (show warning on development builds instead) This PR addresses #3346; an unknown websocket ID should produce a warning during development, but not cause crashes in production RN apps. This PR was created by satya164's request, and was inspired by tanthanh289's suggestion on #3346's thread. On Android, create a websocket using a service like Pusher (`pusher-js` npm package) or manually, and then induce removal of its websocket ID. Result should be a red warning screen during development, and no crash in the app's release variant. [ANDROID] [BUGFIX] [WebSocket] - Prevent unknown websocket IDs from crashing on Android Closes #17884 Differential Revision: D6954038 Pulled By: hramos fbshipit-source-id: b346d80d7568996b8819c0de54552abb534cbfae
Summary: … prevent unknown websocket IDs from crashing on Android (show warning on development builds instead) This PR addresses facebook#3346; an unknown websocket ID should produce a warning during development, but not cause crashes in production RN apps. This PR was created by satya164's request, and was inspired by tanthanh289's suggestion on facebook#3346's thread. On Android, create a websocket using a service like Pusher (`pusher-js` npm package) or manually, and then induce removal of its websocket ID. Result should be a red warning screen during development, and no crash in the app's release variant. [ANDROID] [BUGFIX] [WebSocket] - Prevent unknown websocket IDs from crashing on Android Closes facebook#17884 Differential Revision: D6954038 Pulled By: hramos fbshipit-source-id: b346d80d7568996b8819c0de54552abb534cbfae
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 |
Do you solve it?How to do? @jmsbrett |
I did not solve it. |
+1 |
It's still happening |
+1 - ios as well |
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 messageCannot close WebSocket. Unknown WebSocket id 0
This is because the
websocketFailed
event fires, which callsthis._closeWebSocket(id);
insideWebSocket.js
. Since the connection was never open to begin with, callingWebSocket client = mWebSocketConnections.get(id);
insideWebSocketModule.java
will return null, hence triggering the exceptionthrow new RuntimeException("Cannot close WebSocket. Unknown WebSocket id " + id);
The text was updated successfully, but these errors were encountered: